Lesson 2 · Domain 2 · 18% of the exam

Tool Design & MCP Integration

Everything Domain 2 tests: why tool descriptions — not implementations — decide whether Claude picks the right tool, the Model Context Protocol's three primitives, structured MCP tool errors, .mcp.json configuration and transports, and scoping tool access per agent role. Every concept comes with the real schema, config, or error shape the exam expects you to recognise.

2.1 Why tool design is its own domain

Domain 1 is about how an agent orchestrates itself. Domain 2 is about something narrower and, on the exam, easy to underestimate: how Claude decides which tool to call. The exam's core thesis for this domain is simple to state and easy to forget under time pressure:

Domain 2's central thesisClaude's tool selection accuracy depends almost entirely on how tools are described, not on how they are implemented. A tool that works perfectly on the backend can still be chosen incorrectlyif its description is ambiguous or overlaps another tool's description — the model never sees your code, only the description string.

That reframes a whole category of exam questions. A stem that describes a bug where "the wrong tool gets called" is very rarely testing your knowledge of the tool's internals — it is testing whether you will reach for the description field first. Distractors that propose fixing the backend, adding retries, or upgrading the model are all answering a question that was not asked.

The rest of this lesson builds outward from that thesis: what MCP actually gives you to describe (2.2), how to write descriptions that disambiguate (2.3), how to fail loudly and usefully at the protocol boundary (2.4), how servers get wired into a project (2.5), and how access itself — not just description quality — has to be scoped per agent (2.6).

Maps to blueprint: "tool design principles"

2.2 MCP's three primitives

The Model Context Protocol (MCP) standardises how an application connects Claude to external capabilities and data. A server can expose exactly three kinds of primitive, and the exam expects you to know not just their names but who is in control of each one:

PrimitiveWho invokes itWhat it's for
ToolsThe model — Claude decides when to call one, exactly like Claude API tool_useActions and lookups: run a query, create a ticket, send a message
ResourcesThe host application — not invoked by the model directlyData the client can read and attach to context: a file, a database record, a log
PromptsThe user — explicitly selected, like a slash commandTemplated instructions a server exposes for the user to invoke on demand
MCP Serverone process, three primitive typesmodel decideshost attaches to contextuser selects templateToolsmodel-invoked functionscontrolled by: the modelResourcesapp-controlled context datacontrolled by: the host appPromptsuser-selected templatescontrolled by: the usere.g. run_query(sql)e.g. a file, a DB recorde.g. /summarize-ticket
Fig 2.2 - MCP's three primitives, and who controls each. Tools are the only primitive the model itself decides to invoke.

Illustrative server manifest — one of each primitive

MCP servers don't all expose their capabilities in exactly this JSON shape — this is a simplified, illustrative structure to show the three primitive types side by side, not a literal SDK dump:

{
  "tools": [
    {
      "name": "create_ticket",
      "description": "Create a new support ticket for a customer issue. Use when a new problem is reported that has no existing ticket.",
      "input_schema": { "type": "object", "properties": { "summary": {"type":"string"} } }
    }
  ],
  "resources": [
    {
      "uri": "ticket://{id}",
      "name": "Ticket record",
      "description": "A single support ticket's full history. The host app decides when to attach one to context - the model cannot fetch it directly."
    }
  ],
  "prompts": [
    {
      "name": "summarize_ticket",
      "description": "Template a user can select to summarize a ticket thread.",
      "arguments": [{ "name": "ticket_id", "required": true }]
    }
  ]
}
Classic distractorA question describes a server that only needs to hand Claude read-only reference data (a policy document, a schema) and asks how to expose it. "Add it as a tool" is the planted answer; the correct primitive is a Resource — nothing about that data requires the model to decide when to fetch it.
Maps to blueprint: "MCP primitives: tools, resources, prompts"

2.3 Tool descriptions drive selection

The description field of a tool schema is the onlysignal Claude has about when to use it. It cannot read your implementation code, your tests, or your internal docs — if the description is vague or overlaps another tool's description, misselection is not a bug in Claude, it is a bug in your tool design.

Before: vague, overlapping descriptions

{ "name": "search_docs", "description": "Searches documentation.",
  "input_schema": { "type": "object", "properties": { "query": {"type":"string"} } } }

{ "name": "search_code", "description": "Searches the codebase.",
  "input_schema": { "type": "object", "properties": { "query": {"type":"string"} } } }

Both descriptions are technically true and neither says what the tool is notfor. Ask "where is the refund logic implemented?" and Claude has no textual signal telling it that "implemented" means source code, not prose — it may call either tool, or the wrong one first.

After: precise scope, plus explicit exclusions

{
  "name": "search_docs",
  "description": "Full-text search over markdown documentation in docs/. Use when the user asks how something is documented, explained, or described in prose. Do NOT use for locating function or class definitions - use search_code for that.",
  "input_schema": { "type": "object", "properties": { "query": {"type":"string"} }, "required": ["query"] }
}

{
  "name": "search_code",
  "description": "Full-text search over source files (*.py, *.ts, *.go) for function names, class names, and code identifiers. Use when the user asks where something is implemented or defined. Do NOT use for documentation or prose questions - use search_docs for that.",
  "input_schema": { "type": "object", "properties": { "query": {"type":"string"} }, "required": ["query"] }
}
Vague descriptions - ambiguous selectionClaudewhich tool matches?search_docs"searches stuff"search_code"searches other stuff"both plausible - Claude guesses, sometimes wrongPrecise descriptions - clean selectionClaudeexactly one matchsearch_docsdocs/*.md onlynot code filessearch_codenot selectedsingle clean patheach description states scope + "do NOT use for X"
Fig 2.3 - The description field is the only signal Claude has. Vague, overlapping wording produces an ambiguous choice; precise wording (including what NOT to use a tool for) produces exactly one match.

Two moves did the disambiguating work: stating precisely when each tool applies, and stating explicitly what to use insteadwhen it doesn't. "Do NOT use for X" is not defensive filler — for two tools whose surface behaviour looks similar, it is often the only line separating a clean selection from a coin flip.

Classic distractor"The wrong tool keeps getting called — upgrade to a stronger model." A stronger model narrows the gap but doesn't remove an ambiguity that lives in the schema, not the model. The exam's correct answer is almost always to fix the description.
Maps to blueprint: "tool description quality & selection accuracy"

2.4 Structured, actionable tool errors

Domain 1 taught that tool errors are data the model reasons over, not exceptions that crash the loop — an error result needs a category, a retryability signal, and a suggested next step. That same principle applies with equal force at the MCP server boundary: a well-designed MCP tool never hands the model a raw stack trace or a bare HTTP status code.

Bad: opaque failure

{
  "isError": true,
  "content": [{ "type": "text",
    "text": "Error: Traceback (most recent call last):\n  File \"server.py\", line 88\n  IntegrityError: FK constraint failed (code 409)" }]
}

Claude cannot act on that. It doesn't know if the failure is transient, whether retrying is safe, or what a customer-facing agent should tell the user in the meantime.

Good: structured, actionable failure

{
  "isError": true,
  "content": [{ "type": "text",
    "text": "TICKET_ARCHIVED: Ticket #4471 is archived and cannot be updated directly. This is not retryable as-is. Suggested next step: call reopen_ticket first, or route this to close-notes if the customer only needs a status update." }]
}

Notice the three ingredients: an error category (TICKET_ARCHIVED) the model and your logs can both key off, an explicit retryable signal, and a suggested next action or alternate tool (reopen_ticket). This is the same is_errordiscipline from Domain 1's agentic loop — Domain 2 just asks you to recognise it applies just as strongly when the tool sits behind an MCP server instead of being called in-process.

Classic distractor"Return the underlying HTTP status code so the client can branch on it." A status code alone forces the model (or your code) to maintain a lookup table of meanings; a category plus a suggested action is self-describing and survives the server changing its internals.
Maps to blueprint: "structured tool error handling"

2.5 Configuring MCP servers: .mcp.json and transports

.mcp.json is the project-level configuration file listing the MCP servers that Claude Code — or an Agent SDK application — connects to. Each entry is either a local server launched as a subprocess, or a remote server reached over the network:

{
  "mcpServers": {
    "linear": {
      "command": "npx",
      "args": ["-y", "@example/mcp-server-linear"],
      "env": { "LINEAR_API_KEY": "${LINEAR_API_KEY}" }
    },
    "shared-docs": {
      "type": "http",
      "url": "https://mcp.example.com/docs",
      "headers": { "Authorization": "Bearer ${DOCS_MCP_TOKEN}" }
    }
  }
}
Classic distractorA remote entry with a url but no "type": "http" field. Claude Code reads an untyped entry as a stdio server by default and reports it as misconfigured — the type field is not optional decoration, it is what makes the entry resolve as a remote server at all.

The two entries above show the two transport mechanisms the exam expects you to tell apart:

TransportShape in .mcp.jsonTypical use
stdiocommand + args — spawns a local subprocessLocal dev tools, most common for individual developer setups
Streamable HTTPurl + "type": "http" — connects to a remote, network-reachable serverRemote or shared servers, reachable by multiple clients at once. The older standalone SSE transport still works but is deprecated in favour of Streamable HTTP.
Claude Code / Agent SDK app.mcp.jsonstdio (local)Streamable HTTP (remote)Local MCP serverstdio - subprocesscommand + args, same machineRemote MCP serverStreamable HTTPurl + type, shared across clientsmost common for local dev toolsmulti-client, network-reachable
Fig 2.5 - Two MCP transports. stdio spawns a local subprocess (typical for local dev tools); Streamable HTTP reaches a remote server that can be shared across multiple clients (the older standalone SSE transport still works but is deprecated).

.mcp.json is typically checked into version control so the whole team or CI pipeline shares the same set of MCP servers — that is the point of the file. The corollary the exam tests directly: personal API keys and other secrets must never be committed inside it. Reference them as environment variables (${VAR_NAME}) resolved at runtime, exactly as both entries above do, and keep the actual values in an untracked.env or your CI secret store.

Classic distractor"Commit the API key directly since .mcp.json is only used locally." The file being version-controlled is exactly why a literal secret inside it is wrong — it lands in every clone and the entire commit history, long after the key is rotated.
Maps to blueprint: "MCP server configuration & transports"

2.6 Tool scoping per agent role

Domain 1's anti-pattern #6 — unrestricted tool access — reappears in Domain 2 with an MCP twist: in a multi-agent or multi-MCP-server setup, each agent or subagent should only be connected to the servers and tools it actually needs for its role. A read-only research subagent has no legitimate reason to see a database-write MCP tool, even if that tool is available somewhere in the project.

The key distinction the exam is testing: .mcp.json declares every server the project uses. It does not follow that every agent in that project should have every one of those tools available. Scoping happens one layer down, at the agent or session level:

# .claude/agents/test-runner.md
---
name: test-runner
description: Runs the test suite and reports failures. Use after code changes,
  never for deployment decisions.
tools: Bash, Read, mcp__test-execution__run_tests
---
You run the project's test suite and report failures as {file, test, error}.
You do not deploy, release, or modify infrastructure under any circumstance.

Both a test-execution MCP tool and a deploy MCP tool can exist side by side in the project's .mcp.json. The test-runner subagent's own tools allowlist names only mcp__test-execution__run_tests— the deploy tool is never listed, so this subagent cannot invoke it even if it "discovers" the server is technically reachable. Scoping is a property of the agent definition, not of what the project has wired up.

How the exam asks this"A test-runner subagent and a deploy subagent both exist in a CI pipeline. Both MCP servers are declared in .mcp.json. How should the test-runner's access be restricted?" — the correct answer scopes the allowlist on the subagent itself, not on removing the deploy server from the project or trusting a prompt instruction to hold the line.
Maps to blueprint: "tool access scoping per agent"

Checkpoint quiz — Domain 2

Exam-style: one scenario, one correct answer, three plausible mistakes. Click an option to lock in and see the explanation.

Q1 · Scenario: Developer Productivity

Your team's MCP server exposes search_docs ("Searches documentation.") and search_code ("Searches the codebase."). Claude keeps calling the wrong one for questions like "where is refund logic implemented?" What is the architecturally correct fix?

Q2 · Scenario: Multi-Agent Research System

A research subagent needs the full text of one specific archived report to ground its summary. The MCP server exposes that report as a Resource, not a Tool. What should the architecture do?

Q3 · Scenario: Customer Support Resolution Agent

A refund-processing MCP tool fails because the target ticket is archived. Which tool result is architecturally correct to return to the model?

Q4 · Scenario: Claude Code in CI/CD

You're committing a shared .mcp.json for a CI pipeline. One remote MCP server requires a private API key. What should the committed file contain for that key?

Q5 · Scenario: Code Generation with Claude Code

Both a test-execution MCP tool and a deploy MCP tool are declared in the project's .mcp.json. A test-runner subagent's job during code generation is only to run tests and report failures. How should its tool access be scoped?

Quick glossary

Every term below appears somewhere in this course. If a question uses a word you're unsure of, check here before re-reading the whole lesson.

Agentic loop

The repeating cycle behind any Claude-powered agent: send a request, check what Claude wants to do next (stop_reason), run a tool if asked, send the result back, repeat until Claude is done.

stop_reason

A field on Claude's response telling your code why it stopped talking — e.g. it wants to call a tool (tool_use), it finished (end_turn), or it ran out of room (max_tokens).

tool_use / tool_result

tool_use is Claude asking your code to run something (a search, a database lookup...). tool_result is your code's answer, sent back so Claude can continue.

Subagent / Agent tool

A helper Claude spawns to do one focused piece of work in its own separate memory space, then reports back a summary — so the main conversation doesn't get cluttered with the helper's scratch work.

Hooks (PreToolUse / PostToolUse)

Small scripts that run automatically around a tool call — one right before it (can block it entirely) and one right after (can react to the result). Used for rules that must always hold, not just usually hold.

MCP (Model Context Protocol)

A standard way to connect Claude to external systems — databases, internal tools, company APIs — through a shared plug-in format instead of custom one-off code every time.

MCP Tools vs Resources vs Prompts

The three things an MCP connection can offer: Tools are actions Claude decides to call itself; Resources are data the surrounding app decides to hand Claude; Prompts are ready-made instructions a person picks, like a menu option.

.mcp.json

A config file listing which external MCP connections a project uses — shared with the whole team via version control, with any secret keys kept out of it and read from environment variables instead.

CLAUDE.md

A plain-text file Claude reads automatically at the start of every session in a project — the place for standing instructions like coding conventions or how the codebase is organized.

.claude/rules/

Instructions that only switch on for certain files (e.g. only when editing test files), instead of loading into every single session the way CLAUDE.md does.

Agent Skill

A packaged set of instructions Claude loads by itself when a task matches, without anyone typing a command — think of it as an instruction manual Claude picks up automatically off the shelf.

allowed-tools vs disallowed-tools

Two different settings that sound alike: allowed-tools just skips the usual 'are you sure?' prompt for tools you've pre-approved — it doesn't block anything else. disallowed-tools is what actually removes a tool from what's available.

Plan mode

A mode where Claude describes what it's about to do and waits for a yes before touching any files — used for bigger or riskier changes, skipped for small obvious ones.

Message Batches API

A cheaper way to send Claude a large pile of requests when nobody is waiting on the answer right away — costs about half as much, but can take up to a day to come back.

Prompt caching

A way to avoid Claude re-reading the same large, unchanging block of text (like a system prompt or a codebase) on every single request — it's remembered for a while, which is both cheaper and faster.

Context window

The total amount of conversation and text Claude can 'see' at once for a given request. When a conversation runs long, older parts may need to be summarized or dropped to make room.

Hub-and-spoke (multi-agent)

A setup where one coordinator agent hands out tasks to several helper agents and collects their answers — the helpers never talk directly to each other, only to the coordinator.

Anti-pattern

A design choice that looks reasonable but causes real problems in practice — this course names seven specific ones the exam likes to hide inside wrong answers.

24 people viewing now