Tool Design & MCP Integration — CCA Domain 2 Deep Dive
Domain 2 is 18% of the CCA exam and the domain most candidates underestimate. This lesson covers every task statement — tool interface design, structured error responses, MCP capability types, transport selection, and the tool distribution rule — with the depth the exam actually tests.
Domain 2 is 18% of the CCA exam. That is roughly eleven questions — the third-largest domain by weight, behind Domain 1 (27%) and just under Domains 3 and 4 (20% each), and the one candidates most consistently underestimate. Candidates who treat it as "MCP documentation review" lose those points. The exam tests whether you understand the decisions behind the interface: why descriptions matter more than names, why scope gates must be code and not prompts, why isError is a protocol requirement and not a suggestion.
This lesson covers every task statement in Domain 2 with the depth the exam requires. By the end, you will be able to identify the correct answer on any D2 question in under ten seconds — not because you memorized the answer, but because you understand the reasoning.
Task Statement 2.1 — Tool Interface Design
The tool interface is the contract between your implementation and the model. Every component of that interface shapes how the model uses your tools. Descriptions are not documentation — they are routing instructions.
The Four Principles
Atomic. A tool does one thing and returns one shape. lookup_order_and_send_confirmation is not atomic. It combines two distinct operations, produces output that depends on two different system states, and fails in ways that are indistinguishable: did the lookup fail or the confirmation? Atomic tools are independently testable, independently retryable, and independently debuggable. The exam rewards this decomposition.
Described. The description drives selection. Not the name. This is the single most tested principle in D2. A model choosing between get_order, lookup_order, and retrieve_order_status has ambiguous signal. A model choosing between a tool described as "Retrieves the current fulfillment status of an order by its ID, including tracking number, carrier, and estimated delivery date" and one described as "Gets order details" has a clear signal. Write descriptions that are specific enough that the model knows exactly when to use this tool and when not to.
The practical implication: if two tools have similar names but different purposes, their descriptions must be specific enough to disambiguate. The exam presents scenarios where vague descriptions cause misrouting, and tests whether you identify the description — not the name — as the fix.
Scoped. A tool has access to only what its role requires. This is not just good practice — it is testable at the implementation layer. The scope gate is code, not a prompt instruction. A read_customer_record tool that accepts any database path and relies on the model following instructions to only pass customer paths is not scoped. A tool that validates the path against an allowlist before executing is scoped.
Recoverable. Errors return structured data, not exceptions. When a tool throws, the exception escapes the tool boundary and the model receives an opaque error. It cannot determine what failed, whether the operation is retryable, or what alternative action to take. A tool that returns { ok: false, error: "upstream_timeout", retryable: true } gives the model structured information it can act on.
Input Schema Design
The input schema is part of the interface. Vague schemas produce vague calls.
// VAGUE — model has no signal about valid operations
{
name: "manage_record",
input_schema: {
type: "object",
properties: {
action: { type: "string" },
id: { type: "string" }
},
required: ["action", "id"]
}
}
// PRECISE — model knows the valid actions
{
name: "update_record_status",
input_schema: {
type: "object",
properties: {
id: { type: "string", description: "Record ID in format REC-NNNN" },
status: {
type: "string",
enum: ["active", "pending", "archived"],
description: "New status. 'archived' is irreversible."
}
},
required: ["id", "status"]
}
}
The enum field on status does two things: it constrains valid values and it tells the model what values are meaningful. Without it, the model might call the tool with status: "inactive" or status: "closed" — values your implementation does not handle. The exam tests this: questions about tool misuse often trace back to underspecified input schemas.
Task Statement 2.2 — Structured Error Responses
The isError flag is a protocol requirement. When a tool call fails, returning { content: [...], isError: true } tells the model explicitly that the call did not succeed. The model's behavior diverges based on this flag:
isError: false(or absent): The model treats the response as a successful tool result and continues normal processing.isError: true: The model knows the call failed. It can choose to retry, use a fallback tool, escalate, or inform the user, depending on the context and its instructions.
Without the flag, the model must parse the text content to determine whether the tool succeeded. Natural language parsing for system state is the anti-pattern the exam penalizes across all five domains.
// Server-side tool handler — NEVER let exceptions escape
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params
try {
if (name === "lookup_order") {
const order = await orderService.get(args.id as string)
if (!order) {
return {
content: [{ type: "text", text: JSON.stringify({
error: "Order not found",
errorCode: "NOT_FOUND",
id: args.id
})}],
isError: true
}
}
return {
content: [{ type: "text", text: JSON.stringify(order) }]
}
}
return {
content: [{ type: "text", text: JSON.stringify({ error: "Unknown tool" }) }],
isError: true
}
} catch (err) {
// Catch ALL exceptions — the exception boundary is here, not above
return {
content: [{ type: "text", text: JSON.stringify({
error: "Internal error",
errorCode: "INTERNAL",
retryable: false
})}],
isError: true
}
}
})
The structure of the error payload matters. An error response that includes retryable: true or retryable: false gives the model enough signal to decide whether to attempt the call again. An error response that includes errorCode: "UPSTREAM_TIMEOUT" is more useful than one that says error: "something went wrong" — both because it tells the model what failed and because it enables your monitoring system to categorize errors accurately.
Exam Scenario
The exam presents a customer support agent whose get_shipping_status tool times out when the shipping service is unavailable. The question asks what should happen. The distractors include:
- "The tool should throw an exception and the SDK will handle it" — wrong, exceptions escape the tool boundary
- "The tool should return an empty result set marked as successful" — wrong, this masks the failure and the model continues as if shipping status was successfully retrieved
- "The tool should return a text response saying 'service unavailable'" — technically works but forces the model to parse text for failure detection
The correct answer is always the structured response with isError: true. The model can then apply fallback logic — use cached data, inform the user that real-time status is unavailable, or escalate to human review.
Task Statement 2.3 — Scope Enforcement
Scope enforcement is the principle that a tool's access boundaries are enforced by the implementation, not by the prompt. The CCA exam tests this distinction directly.
Why Prompts Are Not Enforcement
A system prompt instruction — "only read files in the /documents/ directory" — is probabilistic. At a high rate of compliance, you will not notice the failure. At a 1% failure rate, your system occasionally reads files outside its intended scope. For most applications, this is an unacceptable security risk.
Implementation-level enforcement is deterministic:
def read_document(path: str) -> dict:
"""Read a document from the workspace directory."""
# The scope gate is here, not in the system prompt
allowed_base = "/workspace/documents/"
if not path.startswith(allowed_base):
return {
"ok": False,
"error": "Access denied: path outside allowed directory",
"errorCode": "SCOPE_VIOLATION",
"allowedBase": allowed_base
}
# Proceed with the read
try:
content = Path(path).read_text()
return {"ok": True, "content": content}
except FileNotFoundError:
return {"ok": False, "error": "File not found", "errorCode": "NOT_FOUND"}
except Exception as e:
return {"ok": False, "error": "Read failed", "errorCode": "IO_ERROR"}
This tool cannot be tricked into reading outside /workspace/documents/. The model cannot instruct it to do so. A compromised system prompt cannot override it. The scope is a property of the code, not of the language model.
Prerequisite Enforcement
The same principle applies to operation sequencing. If process_refund should only be called after verify_customer_identity has returned a confirmed identity, that prerequisite belongs in a preToolCall hook — not in a system prompt instruction.
// Agent SDK hook — enforces call order at the framework layer
const agent = sdk.createAgent({
tools: ["verify_customer_identity", "process_refund"],
hooks: {
preToolCall: async (context) => {
if (context.toolName === "process_refund") {
const verified = context.session.get("customer_verified")
if (!verified) {
return {
blocked: true,
reason: "Customer identity must be verified before processing a refund. Call verify_customer_identity first."
}
}
}
}
}
})
The hook intercepts the tool call before execution. The model receives a structured response explaining what is required. The enforcement is deterministic — a process_refund call without prior verification is blocked every time, regardless of what the model was told in the system prompt.
Task Statement 2.4 — MCP Capability Types
An MCP server exposes three capability types. The exam tests whether you understand the difference — specifically whether you can distinguish scenarios where each type is the correct choice.
Tools vs Resources vs Prompts
Tools are the most common. They are executable functions the model calls to take actions or retrieve data. The model decides when to call a tool based on the description and the conversation context. Tools have input schemas and return structured responses. All of the error handling discussed above applies to tools.
Resources are read-only data sources the model can reference. They are not called by the model — they are injected into context by the client or retrieved on demand. A resource might be a configuration file, a team standards document, or a database schema. The distinction matters for the exam: a question asking "how should the agent access team coding standards without triggering a tool call on every turn?" has resources as the correct answer.
Prompts are reusable templates that humans invoke, not the model. They are named sequences of instructions the user can trigger from the interface — "run the code-review-template on this file." The model executes within the prompt, but the model did not choose to invoke it. Prompts are confused with tools on the exam because they sound similar. The distinguishing question is: who triggers it?
| Capability | Who invokes | Execution | Schema |
|---|---|---|---|
| Tool | Model (autonomously) | On every call | JSON input schema |
| Resource | Client (on access) | Read-only fetch | URI pattern |
| Prompt | Human (explicitly) | Templated sequence | Argument list |
Transport Selection
The transport choice is not arbitrary. It depends on deployment context:
stdio transport is correct when the MCP server runs as a subprocess on the same machine as the client. This is the standard choice for Claude Code integrations. The server starts when the client starts, communicates over stdin/stdout, and terminates when the client does. No network configuration, no authentication setup, low overhead.
HTTP/SSE transport is correct when the server needs to be accessible from multiple clients, runs on a remote machine, or requires persistent state across sessions. HTTP transport adds authentication requirements — you need to verify that incoming requests are from authorized clients. SSE (Server-Sent Events) enables the server to push updates to the client, which is useful for long-running operations.
The exam tests transport selection in scenarios involving team-shared MCP servers and remote integrations. A question about "an MCP server that the entire engineering team uses for codebase queries" points to HTTP/SSE — not stdio, which is a single-process model.
Task Statement 2.5 — Tool Distribution in Multi-Agent Systems
When a multi-agent system has many tools, how you distribute them across agents determines reliability. This is the most concrete D2 concept tested across multiple exam scenarios.
The problem with tool overload: Above approximately seven tools per agent, selection reliability degrades measurably. Above twelve, misrouting is frequent even with well-written descriptions. The model's attention is spread across a longer tool list, making it harder to identify the most relevant tool for a given operation. The exam uses the number 18 as the canonical overloaded case — it is large enough that the failure mode is obvious in production.
The correct pattern: Distribute tools across specialized agents, each with a focused toolkit. A coordinator agent manages routing but has few tools of its own (primarily the Task tool for subagent delegation). Each specialist agent has the four to seven tools relevant to its domain.
// WRONG: One agent with everything
const agent: AgentDefinition = {
tools: [
"search_web", "fetch_url", "extract_links", "cache_result",
"read_document", "parse_table", "extract_data", "summarize_doc",
"compile_findings", "verify_fact", "format_report", "cite_source",
"send_notification", "log_event", "store_result", "tag_finding",
"escalate_issue", "archive_result"
] // 18 tools — selection reliability will degrade
}
// RIGHT: Tools scoped to role
const webSearchAgent: AgentDefinition = {
description: "Searches the web and caches results for retrieval",
tools: ["search_web", "fetch_url", "extract_links", "cache_result"] // 4 tools
}
const documentAgent: AgentDefinition = {
description: "Reads and analyzes documents and structured data files",
tools: ["read_document", "parse_table", "extract_data", "summarize_doc"] // 4 tools
}
const synthesisAgent: AgentDefinition = {
description: "Compiles research findings into structured reports with citations",
tools: ["compile_findings", "verify_fact", "format_report", "cite_source", "flag_gap"] // 5 tools
}
The exam question is not "is 18 too many?" — every practitioner knows that. The question is "which tool should the web search agent have that would make the current architecture correct?" or "the synthesis agent is occasionally calling the web search tool — what is the most likely cause?" These questions test whether you understand that misrouting is the symptom and vague descriptions plus tool overload are the causes.
CCA Exam Strategy for Domain 2
Domain 2 questions fall into three recognizable patterns:
Pattern 1: "What is wrong with this tool implementation?" You see tool code or a tool definition. The question asks what the failure mode is. Look for: thrown exceptions instead of error returns, missing isError flags, vague descriptions, scope gates implemented as prompt instructions, overloaded toolkits.
Pattern 2: "Which approach correctly enforces X?" You see a requirement (prerequisite ordering, access control, rate limiting). The distractors offer prompt-based solutions. The correct answer is always implementation-level: a hook, a scope gate, or a schema constraint.
Pattern 3: "The agent is misrouting tool calls. What is the most likely cause?" Tool descriptions are the primary lever. The exam tests whether you identify vague or ambiguous descriptions as the root cause before looking at other factors.
The Four Distractors to Know Cold
D2.1 Distractor: "Add an instruction to the system prompt telling the agent to use this tool only when X." Correct answer: put the constraint in the implementation.
D2.2 Distractor: "Return an empty result marked as successful when the tool fails." Correct answer: isError: true with a structured error payload.
D2.3 Distractor: "Give all tools to the coordinator so it can route them to subagents." Correct answer: distribute tools to the appropriate specialist agent, not the coordinator.
D2.4 Distractor: "Resources and prompts are both invoked by the model based on context." Correct answer: resources are accessed by the client; prompts are invoked by the human. Only tools are model-invoked.
Worked Example: Redesigning an Overloaded Tool Set
Here is a scenario in the style of the CCA exam. A customer support agent has the following tool configuration:
const supportAgent = {
tools: [
"get_customer", "get_order", "get_order_history", "get_ticket",
"create_ticket", "update_ticket", "close_ticket", "send_email",
"process_refund", "apply_credit", "flag_fraud", "lookup_address",
"verify_identity", "check_inventory", "get_promotions", "log_interaction"
] // 16 tools
}
The system has an 85% first-contact resolution rate (target: 90%). Analysis shows that process_refund is being called before verify_identity at an 8% rate, and flag_fraud is being called on routine returns.
Diagnosis: Three problems. (1) Tool overload — 16 tools degrades selection reliability. (2) Missing prerequisite enforcement — verify_identity before process_refund must be a hook, not an instruction. (3) Vague description on flag_fraud — the model does not know when fraud detection is appropriate.
Fix:
// Decompose into two agents
const lookupAgent = {
description: "Retrieves customer, order, and ticket records",
tools: ["get_customer", "get_order", "get_order_history", "get_ticket", "verify_identity"]
}
const actionAgent = {
description: "Executes customer service actions after records are confirmed",
tools: ["create_ticket", "update_ticket", "close_ticket", "send_email",
"process_refund", "apply_credit", "log_interaction"],
hooks: {
preToolCall: async (ctx) => {
if (ctx.toolName === "process_refund") {
if (!ctx.session.get("customer_verified")) {
return { blocked: true, reason: "Verify customer identity first using the lookup agent." }
}
}
}
}
}
// Move specialized tools to their own agents
const fraudAgent = {
description: "Reviews transactions for fraud indicators. Use only when purchase pattern is anomalous.",
tools: ["flag_fraud", "lookup_address", "check_inventory"]
}
The refactored system has three agents with focused toolkits (5, 7, 3 tools). The prerequisite is enforced by a hook. The fraud tool's description now includes "use only when purchase pattern is anomalous" — the model has signal to distinguish fraud review from routine returns.
Practice Drill
Work through these before continuing to the next lesson:
- Without looking, name the four principles of tool interface design and give one example of each
- Write the TypeScript structure for a tool handler that returns a structured error when the external service times out — include the isError field
- Explain the difference between MCP tools, resources, and prompts in one sentence each — focus on who invokes each and when
- Given an agent with 16 tools and a 7% misrouting rate: which two changes would have the greatest impact on routing reliability?
- A question on the exam says "the refund tool is being called without identity verification at a 3% rate." Which answer do you select: (a) add prompt instruction, (b) implement preToolCall hook, (c) rename the tool to verify_and_refund, (d) use tool_choice to force ordering?
If you cannot answer question 5 in under five seconds, re-read the scope enforcement section.
Bottom Line
Domain 2 tests whether you treat the tool interface as a contract or as a convenience. Descriptions route selection. Schemas constrain inputs. isError flags signal failure. Scope gates enforce access. Tool count determines selection reliability. Every D2 question on the exam is testing one of these five things.
The domain appears in Scenarios 1, 3, and 4 — customer support, multi-agent research, and developer productivity tools. Three of the four scenarios you will see on exam day likely contain at least one D2 question. This is not a domain to skim.
Proceed to the next lesson for multi-agent orchestration — which builds directly on the tool distribution principles you just covered.