CCAR-F Claude Certified Architect — Foundations

Chapter 06 — MCP Integration and Built-in Tools

Domain 2: Tool Design & MCP Integration (18%) — task statements 2.2, 2.3, 2.5

Two halves: how MCP servers are configured and how MCP errors are structured, then the built-in tool selection table. Both halves are heavily fact-based — this is the chapter where exact strings matter most.

⚠️ Scope note: deploying or hosting MCP servers is explicitly out of scope. You will not be asked about containers, networking, or process supervision. You will be asked which config file a server belongs in.


1. MCP server configuration

📎 The two config locations

File Scope Version-controlled? Use for
.mcp.json (project root) This project, shared with the team Yes — commit it Servers the whole team needs: the company Jira, the internal API, the shared database reader
~/.claude.json (user home) All projects, this user only No Personal servers: your own scratch tools, personal accounts

🎯 The item shape: "the team wants every developer to get the same MCP servers automatically when they clone the repo." → .mcp.json, committed. The distractor is the user-level file, which is per-machine and never shared.

📎 Environment variable expansion

MCP config supports ${VAR} expansion, which is how secrets stay out of committed files:

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_TOKEN": "${GITHUB_TOKEN}"
      }
    },
    "jira": {
      "command": "npx",
      "args": ["-y", "mcp-server-jira"],
      "env": {
        "JIRA_HOST": "${JIRA_HOST}",
        "JIRA_API_TOKEN": "${JIRA_API_TOKEN}"
      }
    }
  }
}

🎯 This is the answer to "how do we commit a shared MCP config without committing credentials?" — commit .mcp.json with ${GITHUB_TOKEN} references; each developer supplies the value in their own environment.

⚠️ Don't overthink this into an out-of-scope answer. Options mentioning secret managers, key rotation, or OAuth flows are outside the blueprint (§4 of the README). The examinable answer is env var expansion.

🎯 Tool discovery timing

Tools from all configured servers are discovered at connection time and are simultaneously available to the agent. Consequences:

  1. You don't "load" a server mid-conversation. If it's configured, its tools are in the tool list from the start.
  2. Every configured server adds to the tool count — which ties directly back to Chapter 05 §2. Configuring six MCP servers "just in case" can push an agent from 5 tools to 25 and degrade selection. Server configuration is tool distribution.

🎯 Prefer community servers for standard integrations

For a standard third-party system — the guide's example is Jira — the answer is to use an existing community MCP server rather than writing your own. Building a custom server for a well-supported integration is effort spent re-solving a solved problem.

The inverse also holds: for your proprietary internal systems, a custom server is the only option. The discriminator in the stem is whether the target system is standard/public or internal/proprietary.

📎 MCP resources — for content catalogs

MCP servers expose resources as well as tools. Resources are addressable content the agent can list and read.

🎯 The examinable use: when an agent is making many exploratory tool calls just to find out what content exists, expose a resource catalog instead. Rather than calling search_docs five times to discover what documentation is available, the agent lists resources once and reads the relevant ones.

The pattern: resources for "what exists," tools for "do something."

⚠️ When the agent prefers a built-in over your MCP tool

A recurring situation: you've added an MCP tool for searching your internal knowledge base, but the agent keeps using the built-in Grep instead.

The fix is to enhance the MCP tool's description so it's clear when that tool is the right choice and what it offers that Grep doesn't. This is Chapter 05's Reflex 3 applied inside Domain 2: the agent is choosing by description, and yours isn't winning.

Distractors here: remove Grep from allowedTools (over-restrictive — Grep is genuinely useful for local code), or instruct the model in the system prompt (probabilistic, and fights the descriptions rather than fixing them).


2. MCP error handling

📎 The isError flag

MCP tool results carry an isError flag. A tool result with isError: true tells the agent the call failed, as opposed to succeeding with unexpected content.

🎯 The critical distinction the exam draws: an access failure is not a valid empty result.

Situation Correct signal If you get it wrong
Query ran, genuinely zero matches isError: false, empty result set
Permission denied / server unreachable / query malformed isError: true with category Agent concludes "there is no data" and reports a confident wrong answer

⚠️ This is one of the most consequential bugs in agentic systems and it appears on the exam: returning [] for a permission failure makes the agent believe the search space is empty. It will then tell the user there are no matching orders, no relevant documents, no such customer — with no indication anything went wrong. Never encode a failure as an empty success.

🎯 The four-category error taxonomy

Memorize these four and their retryability:

Category Meaning Retryable?
Transient Timeout, temporary unavailability, rate limit from the backend Yes — retry with backoff
Validation Malformed input: bad ID format, missing required field Yes, after correction — the agent can fix the input and retry
Business The operation is legitimately disallowed by policy: refund window expired, account closed Noretriable: false. Retrying will never succeed.
Permission The caller lacks authorization for this resource No — needs escalation or different credentials, not a retry

📎 Structured error metadata

An error crossing a boundary should be structured, not a string:

{
  "isError": true,
  "errorCategory": "business",
  "isRetryable": false,
  "message": "Refund window expired. This order shipped 94 days ago; the refund policy covers 90 days from shipment.",
  "attempted": "process_refund(order_id=ORD-10023841, amount=249.00)",
  "alternatives": ["escalate_to_human for policy-exception review"]
}

Each field earns its place:

  • errorCategory — lets the agent choose a strategy class rather than guessing from prose.
  • isRetryable / retriable: false — stops the agent from retrying a business rule forever. 🎯 Business-rule violations must be marked non-retryable; a retry loop against a policy is a named failure.
  • human-readable description — so the agent can explain the situation to the user accurately, including why.
  • what was attempted and alternatives — so the agent's next move is informed.

⚠️ The distractor: a generic "search unavailable" or "operation failed" string. It hides the category, the retryability, and the cause, and forces the agent to guess. Generic errors are a design defect.

🎯 Local recovery, then propagate with partial results

The rule for errors inside a multi-agent system:

  1. Recover locally where possible. A subagent that hits a transient failure retries within itself. It does not bother the coordinator with problems it can solve.
  2. Propagate only what it cannot resolve — and propagate it with partial results, structured context, and suggested alternatives.

Two symmetric anti-patterns, both of which appear as options:

🚫 Anti-pattern Why it's wrong
Silently suppressing the error and returning success Downstream agents build on a false premise; the final output is confidently incomplete with no indication
Terminating the entire workflow because one subagent failed Discards all the successful work; three of four research streams completed fine

The correct behaviour is the middle path: complete what you can, report precisely what you couldn't, and annotate the gap in the output. In Scenario 3, that means the synthesis agent's report carries coverage annotations — "regulatory filings could not be retrieved (permission denied); the following sections draw only on press coverage."


3. Built-in tools — the selection table

Task statement 2.5. Scenario 4 (Developer Productivity) is built on this. Pure recall; know it exactly.

📎 Tool Purpose Use when
Grep Search file contents You know roughly what the code says: a function name, a string literal, an import
Glob Match file paths by pattern You know the naming shape: **/*.test.tsx, src/**/*.py
Read Read a full file You need the whole file's content and structure
Write Write a full file Creating a new file, or replacing one wholesale
Edit Targeted change via unique text match Changing a specific known passage inside a larger file
Bash Run shell commands Builds, tests, git, anything with a CLI

🎯 Grep vs Glob

The single most likely built-in-tools item. Grep searches inside files; Glob matches filenames.

  • "Find every file that calls processPayment" → Grep
  • "Find every test file in the project" → Glob with **/*.test.tsx

⚠️ If the stem gives you a path pattern — an extension, a directory shape, a filename convention — the answer is Glob, even though grepping for the same string might also work. The tool that matches the intent wins.

🎯 Edit vs Read + Write

Edit requires a unique text anchor. It locates the passage by exact match and replaces it. If the string you want to change appears more than once, Edit cannot disambiguate and will fail.

The fallback is Read + Write: read the whole file, construct the modified content, write it back. This is the documented answer for when Edit's anchor isn't unique — for example, changing the third occurrence of a repeated boilerplate block, or reformatting a file where the target text is inherently repetitive.

Situation Tool
Change one clearly unique passage Edit
Target text appears multiple times, no unique surrounding context Read + Write
Create a new file Write
Restructure most of a file Read + Write

⚠️ Don't reflexively answer Edit because it's the more precise tool. The exam tests whether you know its precondition.

🎯 Incremental codebase understanding

The pattern for exploring an unfamiliar codebase (Scenario 4's core activity):

1. Grep for entry points        → find main(), route definitions, exported handlers
2. Read those files             → understand structure and follow the imports
3. Grep / Glob the imports      → locate the next layer
4. Repeat, narrowing            → build understanding incrementally

Grep to locate, Read to follow. The point is that you never load the whole codebase — you traverse it. 🧠 This connects to Domain 5: uncontrolled exploration floods context, which is why the Explore subagent exists (Chapter 08 §2) to isolate verbose discovery from the main session.


4. Worked examples

Example 1 — error signaling

A research subagent queries an internal document repository. When its credentials lack access to a collection, the MCP server returns an empty result array with isError: false. The final report states that no relevant internal documents exist. What is the primary problem?

A. The subagent should retry the query with different parameters. B. The server is reporting an access failure as a valid empty result. C. The coordinator should have assigned a different subagent. D. The report generator should note when sections have no sources.

Answer and reasoning

B.

The permission failure is being encoded as a successful search that found nothing. Every downstream conclusion is built on a false premise, and nothing in the system can detect the difference. The server must set isError: true with errorCategory: "permission" and isRetryable: false.

  • A — Retrying won't help: the failure is authorization, not query formulation. And the subagent doesn't know there was a failure to retry.
  • C — A different subagent with the same credentials hits the same wall.
  • D — A reasonable additional safeguard (coverage annotation, Chapter 12 §4), but it treats the symptom. With isError: false, "no sources" and "no access" are indistinguishable, so the annotation would be wrong too.

Reflex applied: #6. Distinguish access failures from valid empty results; never encode failure as success.

Example 2 — built-in selection

An agent must locate all React test files across a large monorepo where tests live alongside their components in many directories. Which built-in tool is most appropriate?

A. Glob with the pattern **/*.test.tsx B. Grep for the string describe( C. Read on each directory's index file D. Bash with a find command

Answer and reasoning

A.

The task is defined by a filename pattern spread across directories — exactly Glob's purpose. **/*.test.tsx matches at any depth in one call.

  • B — Grep searches contents. It would find test files, but also any file mentioning describe( in a comment or utility, and it would miss test files using a different framework idiom. Wrong tool for a path-shaped question.
  • C — Doesn't scale and depends on index files enumerating tests, which they don't.
  • D — Works, but reaches for the shell when a purpose-built tool exists. On this exam, the dedicated tool beats shelling out.

Reflex applied: path pattern → Glob; content → Grep.


5. Decision reflexes (reproduce from memory)

  • .mcp.json (project, committed, team-shared) vs ~/.claude.json (user, personal, not shared).
  • Secrets in shared config → ${ENV_VAR} expansion. Not secret managers, not OAuth — those are out of scope.
  • All configured servers' tools are discovered at connection time and are simultaneously available → every server you add is tool-count pressure.
  • Standard integration (Jira) → community MCP server. Proprietary internal system → custom server.
  • Many exploratory calls just to discover what exists → MCP resources as a content catalog.
  • Agent prefers built-in Grep over your MCP tool → improve the MCP tool's description, don't remove Grep.
  • isError: true for failures. An access failure is not an empty result — that bug makes the agent confidently report "nothing exists."
  • Error categories: transient (retry) · validation (correct, then retry) · business (retriable: false) · permission (escalate, don't retry).
  • Structured error = category + retryability + human description + what was attempted + alternatives. Generic "operation failed" is a defect.
  • Recover locally; propagate only the unresolvable, with partial results. Never suppress silently; never kill the whole workflow.
  • Grep = contents · Glob = paths · Read/Write = whole file · Edit = unique text anchor · Bash = commands.
  • Edit needs a unique anchor; when it isn't unique, fall back to Read + Write.
  • Explore a codebase with Grep for entry points → Read to follow imports, incrementally.