Fourth in the 6-part CCAR-F scenario series — full source for every scenario: labs.
The scenario, as written in the exam guide
An agent helps engineers explore unfamiliar codebases, understand legacy systems, and automate repetitive tasks using built-in tools (Read, Write, Bash, Grep, Glob) and MCP servers.
| Domain | Weight | Where it shows up in this scenario |
|---|---|---|
| Tool Design & MCP Integration | 18% | Which tools are generic and which are custom-built — and why |
| Claude Code Configuration & Workflows | 20% | Mirrors Claude Code's real built-in/MCP split, made explicit |
| Agentic Architecture & Orchestration | 27% | One loop, two tool sources, one system prompt telling the model which to reach for |
Built-ins and MCP servers aren't interchangeable
This scenario pairs two tool sources on purpose, and the exam-relevant judgment isn't syntax — it's knowing which one a given question actually needs. labs's standalone script (it doesn't run inside Claude Code, so it can't use the real built-ins) implements generic equivalents — read_file, grep_codebase, list_files — and pairs them with three custom MCP tools: get_file_history, find_owner, find_related_tests.
The line between them: can a generic tool answer this, given enough calls? get_file_history, find_owner, and find_related_tests all answer questions that aren't derivable from a file's own text, no matter how much reading and grepping you throw at it — why the code looks the way it does, who to actually contact, whether it has coverage. None of that lives inside the file. That's what justifies a custom tool.
The inverse mistake is just as real, and worth naming explicitly: a bespoke read_discount_validation_logic tool that's really just read_file with a narrower name adds surface area to maintain without adding capability. If the generic tool can already answer it, a custom one is theater, not architecture.
The example is built to catch a specific failure
The target codebase is a small, deliberately booby-trapped discount-code module. legacy-promo-engine.ts has zero comments explaining why LEGACY- codes route through a separate lookup table — you only learn that from get_file_history (a 2021 campaign) and find_owner (the original authors are gone; escalate to #platform-oncall). apply-discount.ts has real branching logic and zero test coverage — find_related_tests returns an empty array, not an error, for exactly this file.
An agent that only used read_file/grep_codebase could describe what apply-discount.ts does in perfect detail and never once surface that it's unguarded by tests. That gap — competent code explanation with no risk flag — is precisely what this scenario is checking whether an agent (and a candidate) will catch.
Run it yourself
git clone https://github.com/aicertstudy/labs
cd labs/ccar-f/scenarios/scenario-4-developer-productivity
npm install && npm test # 3 passing tests, discovered inside sample-codebase/
cp .env.example .env # add your ANTHROPIC_API_KEY
npm run agent -- "How do LEGACY- discount codes work, and who do I ask if one misbehaves?"
Watch the tool-call log: reading/grepping for the what, then the MCP tools for the why and who — ideally before the agent makes any claim about risk or ownership.
Test yourself
1. What's the deciding question for whether a new capability should be a custom MCP tool versus something achievable with generic Read/Grep-style tools?
Answer: whether a generic tool could answer it given enough calls — if yes, a custom tool adds maintenance surface without adding capability.
Speed or convenience alone doesn't justify a bespoke tool. The justification is informational: does the answer exist somewhere a generic file/search tool structurally cannot reach (history, ownership records, a coverage index)?
2. Why does find_related_tests return an empty array for apply-discount.ts instead of throwing an error?
Answer: an empty array is a meaningful, distinct answer — "no coverage exists" — not a failure of the tool to find something.
Treating "no results" as an error would make it indistinguishable from a broken lookup or a typo'd path. A typed empty result lets the calling agent (and a human reading the code) treat "genuinely no tests" as its own real, actionable fact.
3. Why does legacy-promo-engine.ts deliberately have no comment explaining why LEGACY- codes exist?
Answer: so answering "why does this code exist" requires the history/ownership tools, not just reading the file — demonstrating why those tools exist at all.
If the reasoning were in a comment, a generic read_file call would already answer the question, and the custom MCP tools would have nothing distinct to contribute. The gap is intentional, to make the domain's actual lesson observable rather than theoretical.
4. What failure mode does an agent exhibit if it explains apply-discount.ts's logic accurately but never checks find_related_tests?
Answer: competent code explanation with no risk flag — it correctly describes what untested, legacy-adjacent logic does without surfacing that it's unguarded by tests.
Accuracy about behavior isn't the same as surfacing risk. An agent (or an engineer) can be completely correct about what code does and still fail the actual job of flagging that a change here has no safety net.
5. Would adding a fourth MCP tool, find_function_definition(name), that's implemented internally as a grep, be good tool design by this scenario's standard?
Answer: no — if it's just grep_codebase with a narrower name and no information a generic search couldn't already surface, it's the "bespoke tool that's really a generic one in disguise" mistake the README warns about.
The standard isn't "does this tool make one specific task slightly more convenient" — it's "does this tool reach information a generic tool structurally cannot." A grep-backed tool with a specific name fails that test even if it's pleasant to call.
Free CCA-F practice exam, covering all 5 domains: aicert.study/certifications/ccaf.
