Last reviewed: May 2026
Exercise the CCA-F exam topics from your terminal — Claude Code CLI commands, Anthropic API calls, MCP servers, and multi-agent patterns, each tied back to an exam domain.
By the end of this lab you will have installed Claude Code, configured a project with CLAUDE.md, built a custom slash command, wired up an MCP server, exercised the tool-use API with structured output, explored extended thinking, and built a multi-agent supervisor pipeline — covering all five CCA-F exam domains from your terminal. No cloud account or billing required; everything runs locally.
node --version)ANTHROPIC_API_KEY in your shell (export ANTHROPIC_API_KEY=sk-ant-...)mkdir cca-f-lab && cd cca-f-lab)API calls to Anthropic incur usage-based charges. Steps 6–8 and 10 use claude-sonnet-4-6 which costs ~$3/M input tokens and ~$15/M output tokens. The total cost for this lab is typically under $0.15. Extended thinking (Step 7) uses additional thinking tokens billed at the output rate.
Claude Code is the CLI that the CCA-F exam tests most directly — configuration, hooks, slash commands, and permission modes all live here. We start by installing it globally so every later step can call claude.
After installation, a quick claude --version proves the binary is on your PATH, and claude --print-system-prompt confirms the API key is valid (it makes a lightweight call under the hood).
# Install Claude Code globally
npm install -g @anthropic-ai/claude-code
# Verify installation
claude --version
# Confirm API connectivity (prints the built-in system prompt)
claude --print-system-prompt | head -20The exam heavily tests the three-tier CLAUDE.md hierarchy: user (~/.claude/CLAUDE.md), project (repo root), and module (subdirectories). Each level inherits from the one above and can override it.
We will create a project-level file that sets coding conventions and a user-level file that sets personal preferences. When Claude Code reads the project, it merges all three tiers — exactly the behavior the exam asks about.
# Create project root CLAUDE.md
cat > CLAUDE.md << 'EOF'
# Project: CCA-F Lab
## Coding conventions
- Use TypeScript for all new files
- Prefer const over let
- Use single quotes for strings
- No semicolons (Prettier default)
## Testing
- Run `npm test` before committing
- All new functions must have at least one test
EOF
# Create user-level CLAUDE.md (applies to ALL your projects)
mkdir -p ~/.claude
cat > ~/.claude/CLAUDE.md << 'EOF'
# User preferences
- Be concise in responses
- Prefer functional programming patterns
- Always explain the "why" before showing code
EOF
# Verify Claude Code sees both files
claude "What instructions do you see in CLAUDE.md?" --printCustom slash commands live in .claude/commands/ as Markdown files. The filename becomes the command name (e.g. scaffold.md → /scaffold). The file content is the prompt template — it can include $ARGUMENTS for user input.
This is a high-frequency exam topic: where commands live, how arguments are passed, and how they differ from hooks.
# Create the commands directory
mkdir -p .claude/commands
# Create a scaffold command
cat > .claude/commands/scaffold.md << 'EOF'
Create a new TypeScript module named $ARGUMENTS with:
1. A main source file at src/$ARGUMENTS/index.ts
2. A test file at src/$ARGUMENTS/__tests__/index.test.ts
3. Export the module from the project root index.ts
Follow the coding conventions in CLAUDE.md.
EOF
# Test the command (Claude Code will execute it interactively)
claude /scaffold calculatorHooks run shell commands in response to Claude Code events. The exam tests three hook types: PreToolUse (before a tool runs), PostToolUse (after), and Notification (on status changes). Hooks are defined in .claude/settings.json.
We will add a PostToolUse hook that runs a linter after every file edit — a common production pattern that the exam frequently asks about.
# Initialize a package.json so we have a lint script
npm init -y
npm install --save-dev typescript @typescript-eslint/parser
# Create project-level settings with a post-tool hook
mkdir -p .claude
cat > .claude/settings.json << 'EOF'
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "npx tsc --noEmit 2>&1 | head -5"
}
]
},
"permissions": {
"allow": [
"Read",
"Write",
"Edit"
]
}
}
EOF
# Verify settings are recognized
cat .claude/settings.jsonThe Model Context Protocol (MCP) is a core exam domain. MCP servers expose tools, resources, and prompts to LLM clients via a standardized JSON-RPC protocol. The stdio transport is the simplest — the server reads from stdin and writes to stdout.
We will build a minimal MCP server that exposes a get_weather tool, then configure Claude Code to connect to it. This exercises the full MCP lifecycle the exam tests: tool definition, transport wiring, tool invocation, and result handling.
# Install the MCP SDK
npm install @modelcontextprotocol/sdk
# Create the MCP server
cat > mcp-weather.mjs << 'EOF'
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new McpServer({ name: "weather", version: "1.0.0" });
server.tool("get_weather", { city: { type: "string" } }, async ({ city }) => ({
content: [{ type: "text", text: `Weather in ${city}: 72°F, sunny` }],
}));
const transport = new StdioServerTransport();
await server.connect(transport);
EOF
# Register the MCP server in Claude Code settings
cat > .claude/settings.json << 'EOF'
{
"mcpServers": {
"weather": {
"command": "node",
"args": ["mcp-weather.mjs"]
}
}
}
EOF
# Ask Claude Code to use the MCP tool
claude "What is the weather in Tokyo? Use the get_weather tool."The exam tests the Messages API tool-use flow in detail: you send tools in the request, the model responds with a tool_use content block, you execute it and return a tool_result, then the model produces the final answer.
This step uses curl to call the API directly — the most exam-aligned approach since the exam tests the raw request/response format, not SDK wrappers.
# Call the Messages API with a tool definition
curl -s https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"max_tokens": 1024,
"tools": [{
"name": "calculate",
"description": "Evaluate a math expression",
"input_schema": {
"type": "object",
"properties": {
"expression": { "type": "string", "description": "Math expression to evaluate" }
},
"required": ["expression"]
}
}],
"messages": [{
"role": "user",
"content": "What is 15% tip on a $85.50 dinner bill?"
}]
}' | python3 -m json.toolExtended thinking lets the model use a thinking block to reason step-by-step before responding. The exam tests when to use it (complex reasoning, math, code generation) and how to configure budget_tokens.
We send a deliberately tricky logic puzzle and compare responses with and without extended thinking to see the quality difference firsthand.
# Request WITH extended thinking
curl -s https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"max_tokens": 16000,
"thinking": {
"type": "enabled",
"budget_tokens": 10000
},
"messages": [{
"role": "user",
"content": "A farmer has 17 sheep. All but 9 run away. How many does he have left? Explain your reasoning step by step."
}]
}' | python3 -m json.tool
# The response includes a "thinking" block showing the model's
# internal reasoning before the final answer.Prompt caching lets you mark parts of the request as cacheable. Cached prefixes cost 90% less on subsequent calls and have zero additional latency. The exam tests the cache_control block placement and the cache-hit headers.
We send the same large system prompt twice and check the response headers to confirm the cache hit on the second call.
# First call — creates the cache entry
curl -s -D /dev/stderr https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"max_tokens": 256,
"system": [{
"type": "text",
"text": "You are a helpful assistant specialized in cloud architecture. You follow AWS Well-Architected Framework principles. You always recommend infrastructure as code. You prefer Terraform over CloudFormation. You prioritize security and cost optimization.",
"cache_control": { "type": "ephemeral" }
}],
"messages": [{ "role": "user", "content": "How should I set up VPC peering?" }]
}' > /dev/null 2>&1
# Second call — should hit the cache (check usage.cache_read_input_tokens)
curl -s https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"max_tokens": 256,
"system": [{
"type": "text",
"text": "You are a helpful assistant specialized in cloud architecture. You follow AWS Well-Architected Framework principles. You always recommend infrastructure as code. You prefer Terraform over CloudFormation. You prioritize security and cost optimization.",
"cache_control": { "type": "ephemeral" }
}],
"messages": [{ "role": "user", "content": "What about NAT gateway sizing?" }]
}' | python3 -c "import sys,json; d=json.load(sys.stdin); print(f\"Cache read tokens: {d['usage'].get('cache_read_input_tokens', 0)}\")"The exam tests how to run Claude Code non-interactively — the --print flag outputs the result to stdout without an interactive session, and --output-format json gives structured output for scripting.
This is the pattern used in CI pipelines: feed a prompt, get a result, parse it programmatically.
# Headless mode: prompt in, text out
claude --print "List 3 TypeScript best practices, one per line"
# JSON output for programmatic use
claude --print --output-format json "What is 2+2?"
# Pipe a file for code review (CI pattern)
echo 'const x: any = "hello";' > example.ts
claude --print "Review this TypeScript file for type safety issues: $(cat example.ts)"Domain 1 (Agentic Architecture, 27%) is the highest-weighted on the exam. The supervisor pattern — one orchestrator dispatching to specialist sub-agents, each with its own system prompt and tool set — is the most-tested architecture.
We will build a self-contained Node.js script that implements a supervisor coordinating two specialists: a researcher that reads files and summarizes, and a writer that produces output. The supervisor decomposes a task, delegates to each specialist via separate API calls, and aggregates the results. Run it from the terminal and watch the delegation happen live.
# Install the Anthropic SDK
npm install @anthropic-ai/sdk
# Create sample data for the agents to work with
cat > sales-data.csv << 'EOF'
month,revenue,units
Jan,12500,150
Feb,15200,180
Mar,11800,140
Apr,18900,220
May,22100,260
Jun,19500,230
EOF
# Create the multi-agent supervisor script
cat > supervisor.mjs << 'SCRIPT'
import Anthropic from "@anthropic-ai/sdk";
import { readFileSync } from "fs";
const client = new Anthropic();
async function callAgent(role, systemPrompt, userMessage) {
console.log(`\n--- ${role} agent ---`);
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
system: systemPrompt,
messages: [{ role: "user", content: userMessage }],
});
const text = response.content
.filter((b) => b.type === "text")
.map((b) => b.text)
.join("\n");
console.log(text);
return text;
}
// --- Supervisor: decompose, delegate, aggregate ---
const data = readFileSync("sales-data.csv", "utf-8");
console.log("=== SUPERVISOR: Decomposing task ===");
console.log("Task: Analyze sales data and write a brief executive summary.\n");
console.log("Step 1: Delegate analysis to Researcher agent");
console.log("Step 2: Delegate writing to Writer agent");
console.log("Step 3: Combine results\n");
// Sub-agent 1: Researcher — extracts insights
const analysis = await callAgent(
"Researcher",
"You are a data analyst. Extract key trends, highs, lows, and growth rates. Be precise with numbers. Output bullet points only.",
`Analyze this CSV data:\n\n${data}`
);
// Sub-agent 2: Writer — produces the summary from the analysis
const summary = await callAgent(
"Writer",
"You are a business writer. Write a 3-sentence executive summary from the analysis provided. Use a professional tone. Include specific numbers.",
`Write an executive summary based on these findings:\n\n${analysis}`
);
console.log("\n=== SUPERVISOR: Final aggregated output ===");
console.log(summary);
SCRIPT
# Run the supervisor
node supervisor.mjsThis lab runs entirely locally — no cloud resources to tear down.
# Remove the lab directory
cd .. && rm -rf cca-f-lab
# Optionally remove the user-level CLAUDE.md we created
rm -f ~/.claude/CLAUDE.md
# Optionally uninstall Claude Code
npm uninstall -g @anthropic-ai/claude-code
This lab focuses on hands-on CLI and API exercises. It does not cover: