Recommended resources
The freely available resources this lesson will direct you to.
Welcome back! In our previous lesson, we designed the architectural blueprint for a powerful, integrated workflow. We mapped out how a custom slash command, a security hook, and an MCP server would work together to create a safe and efficient process for scaffolding new data models in a FastAPI project.
Today, we move from blueprint to reality. Your goal is to implement and debug this integrated workflow, ensuring the slash command, hook, and MCP server communicate correctly. This is a hands-on session where you'll write the code for each component and, more importantly, learn the techniques to verify and troubleshoot their interactions. This process of building and debugging is fundamental to creating reliable AI-powered automations.
1. Setting Up the Project Structure
First, let's establish the file structure for our components. Within your project, you'll work primarily inside the .claude/ directory.
Create the following structure:
.
├── .claude/
│ ├── skills/
│ │ └── new-model/
│ │ └── SKILL.md
│ ├── hooks/
│ │ └── protect-base-schema.sh
│ ├── mcp/
│ │ └── audit_server.py
│ ├── .mcp.json
│ └── settings.json
└── app/
├── db/
│ └── base.py # Our protected file
├── models/
└── schemas/
We'll populate these files as we proceed.
2. Implementing the Auditing MCP Server
Our workflow requires an external service to log schema changes. We'll build this as a simple FastAPI application that Claude Code will manage as an MCP server.

Given your expertise in FastAPI, the application code itself is straightforward. The key is understanding how Claude Code interacts with it.
File: .claude/mcp/audit_server.py
Create the following Python script. This server will have a single endpoint, /log, to receive audit events.
import uvicorn
from fastapi import FastAPI, Request
import json
import logging
# Configure logging to print to stdout
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
app = FastAPI()
@app.post("/log")
async def log_event(request: Request):
"""
Receives and logs an audit event.
"""
try:
data = await request.json()
# In a real application, this would write to a database or structured log service.
# For our purposes, printing to the console is sufficient to verify the workflow.
logging.info(f"AUDIT LOGGED: {json.dumps(data)}")
return {"status": "success", "message": "Event logged successfully"}
except json.JSONDecodeError:
logging.error("Failed to decode JSON from request body")
return {"status": "error", "message": "Invalid JSON format"}, 400
except Exception as e:
logging.error(f"An unexpected error occurred: {e}")
return {"status": "error", "message": str(e)}, 500
if __name__ == "__main__":
# Claude Code will run this script and expects the server to be on port 8000
uvicorn.run(app, host="0.0.0.0", port=8000)
Next, we must tell Claude Code about this server.
File: .claude/.mcp.json
This file registers our FastAPI app as an MCP server named audit.
{
"mcpServers": {
"audit": {
"type": "stdio",
"command": "python",
"args": [
".claude/mcp/audit_server.py"
],
"env": {}
}
}
}
When you start a Claude Code session in this project, it will execute python .claude/mcp/audit_server.py and make its tools available under the mcp__audit__* namespace.
3. Implementing the /new-model Slash Command
Now for the user-facing trigger. We'll implement the /new-model command as a "skill," which is the modern and more capable successor to the original custom commands.
File: .claude/skills/new-model/SKILL.md
This file defines the command's behavior and the multi-step prompt that orchestrates the entire workflow.
---
name: new-model
description: "Scaffolds a new Pydantic/SQLAlchemy model and logs the event."
argument-hint: "[ModelName]"
disable-model-invocation: true
allowed-tools:
- Write
- mcp__audit__log
---
You are an expert FastAPI developer specializing in database modeling. Your task is to scaffold a new data model based on the provided name: **$ARGUMENTS**.
Follow these steps precisely:
1. **Generate Pydantic Schema:** Create a Pydantic schema for `$ARGUMENTS`. Name it `{$ARGUMENTS}Schema`. Place it in a new file at `app/schemas/$ARGUMENTS_lower.py`. The schema should include a basic `id: int` and a `name: str` field.
2. **Generate SQLAlchemy Model:** Create a corresponding SQLAlchemy model for a table named `$ARGUMENTS_lower`. Name the class `$ARGUMENTS`. Place it in a new file at `app/models/$ARGUMENTS_lower.py`. It should correspond to the Pydantic schema.
3. **Log the Creation Event:** Use the `mcp__audit__log` tool to record this action. The `tool_input` for the MCP tool should be a JSON object with this exact structure: `{"event_type": "MODEL_CREATE", "model_name": "$ARGUMENTS"}`.
4. **Confirm Completion:** Once all steps are successfully completed, report back that the models have been created and the event has been logged.
This skill uses the $ARGUMENTS placeholder to dynamically insert the model name. It also explicitly lists the tools it's allowed to use, which is a good security practice.
4. Implementing the PreToolUse Security Hook
Finally, we'll create our automated guardian. This shell script will block any attempt to modify the app/db/base.py file.
File: .claude/hooks/protect-base-schema.sh
#!/bin/bash
# Read the hook input from stdin
HOOK_INPUT=$(cat)
# Use jq to safely extract the file_path. Handle cases where it might be null.
FILE_PATH=$(echo "$HOOK_INPUT" | jq -r '.tool_input.file_path // ""')
# Define the protected file
PROTECTED_FILE="app/db/base.py"
# Check if the file path matches the protected file
if [[ "$FILE_PATH" == "$PROTECTED_FILE" ]]; then
# If it matches, print an error to stderr and exit with code 2 to block the action.
echo "Error: Modification of the core schema file ($PROTECTED_FILE) is forbidden by a hook." >&2
exit 2
fi
# If it does not match, exit with code 0 to allow the action.
exit 0
Make the script executable: chmod +x .claude/hooks/protect-base-schema.sh.
Now, register the hook in Claude Code's settings.
File: .claude/settings.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"type": "command",
"command": "bash .claude/hooks/protect-base-schema.sh"
}
]
}
}
This configuration tells Claude Code to run our script before any Write or Edit tool is used. The script's exit code determines the outcome.
To reinforce this crucial mechanism, watch this short clip.
How Claude Code Hooks Save Me HOURS Daily
The video 'How Claude Code Hooks Save Me HOURS Daily' by Better Stack offers a concise demonstration of using an exit code of 2 to block an action and steer the model. This is the exact mechanism our security hook relies on.
Watch the segment from 01:28 to 01:54. Pay close attention to how Claude attempts to use npm, the hook intercepts this, returns an error with an exit code, and Claude then corrects its course to use bun instead. This demonstrates the power of hooks to enforce rules.
5. Bringing It All Together: Testing and Debugging
With all components in place, it's time to test the entire workflow. Debugging integrated systems like this is a skill in itself. It requires systematic testing and knowing where to look for information.
Test Plan
-
The "Happy Path":
- Action: In your Claude Code terminal, run:
/new-model UserProfile - Expected Outcome:
- Claude should state it's creating the files.
- Two new files should appear:
app/schemas/userprofile.pyandapp/models/userprofile.py. - The console where you launched Claude Code should show the output from our
audit_server.py, including the line:AUDIT LOGGED: {"event_type": "MODEL_CREATE", "model_name": "UserProfile"}. - Claude should confirm completion of all steps.
- Action: In your Claude Code terminal, run:
-
The "Guardrail Path":
- Action: Give Claude a prompt that would force it to edit the protected file, e.g.,
"Add a comment to the top of app/db/base.py saying 'Hello World'" - Expected Outcome:
- Claude will propose to use the
Edittool onapp/db/base.py. - The action will be blocked.
- Claude should display the error message from our hook: "Error: Modification of the core schema file (app/db/base.py) is forbidden by a hook." It will then report that it cannot complete the request.
- Claude will propose to use the
- Action: Give Claude a prompt that would force it to edit the protected file, e.g.,
The Debugging Toolkit
If things don't work as expected, don't worry. This is where real learning happens. Here is a systematic guide to troubleshooting, based on a comprehensive debugging resource.
Debugging Claude Code Skill | Agent Skills
The 'Debugging Claude Code' skill page is an essential reference for any power user. It provides a structured approach to diagnosing issues with commands, hooks, and MCP servers.
Familiarize yourself with this debugging guide. Specifically, review the following sections: Quick Diagnostics: Commands like claude doctor and claude --print-hooks. Common Issues Quick Reference: A table of symptoms and fixes. Diagnostic Commands: Pay attention to the commands for checking hooks (/hooks) and MCP servers (/mcp). Diagnostic Decision Tree: The flowcharts for 'Are tools working?' and 'Are hooks working?' provide an excellent mental model for troubleshooting. Built-in Diagnostic Commands: A handy list of in-session commands.
Here's how to apply those principles to our workflow:
-
Problem: The
/new-modelcommand isn't found.- Check: Is the
SKILL.mdfile in the right place (.claude/skills/new-model/SKILL.md)? - Check: Is there a syntax error in the frontmatter YAML?
- Check: Is the
-
Problem: Claude says it can't use the
mcp__audit__logtool.- Check MCP Status: Run
/mcpin the session. Does it show theauditserver as connected? - Check Server Logs: Look at the terminal where you launched Claude Code. Did the
audit_server.pystart? Are there any errors from FastAPI or Uvicorn? - Check Permissions: The skill's frontmatter includes
mcp__audit__loginallowed-tools. If you were running this without a skill, you might need to grant permission with/permissions.
- Check MCP Status: Run
-
Problem: The hook doesn't block the edit on the protected file.
- Check Hook Registration: Run
/hooksorclaude --print-hooks. Is yourPreToolUsehook listed with the correct matcher and command? - Check Hook Script: Is the script executable (
chmod +x)? Does it have the correct path insettings.json? - Test the Hook Manually: You can simulate Claude's input to test your script directly, as suggested in the
claude-code-guide.
This should print your error message and the exit code should beecho '{"tool_name":"Edit","tool_input":{"file_path":"app/db/base.py"}}' | .claude/hooks/protect-base-schema.sh # Check the exit code echo $?2.
- Check Hook Registration: Run
-
Problem: You can't see what's happening.
- Use Verbose Mode: Press
Ctrl+Oin your session to toggle verbose output. This will show you the tool calls Claude is making and the hook executions in real-time. - Use Debug Flags: For maximum detail, restart Claude Code with
claude --debug. This provides a firehose of information, including the full JSON payload sent to hooks.
- Use Verbose Mode: Press
By methodically checking each point of communication—from the user's command to the hook's script logic to the MCP server's network availability—you can isolate and resolve any issue.
Conclusion
Congratulations! You have successfully implemented and debugged a complex, multi-component workflow in Claude Code. You've gone beyond using individual features in isolation and have orchestrated them into a cohesive system that is both powerful and safe.
Key Takeaways:
- Implementation Follows Design: A clear architectural plan makes implementation a matter of translating logic into the correct file formats (Python, Markdown, JSON, Shell).
- Communication is Key: The workflow relies on well-defined communication channels: argument passing to skills, HTTP requests to MCP servers, and
stdin/exit codes for hooks. - Debugging is Systematic: Troubleshooting is not guesswork. By using commands like
/mcp,/hooks, and debug flags, you can methodically trace the flow of execution and pinpoint failures. - Exit Code
2is a Powerful Tool: This simple mechanism is the foundation of creating deterministic guardrails that give you ultimate control over the AI's actions.
Next Up:
Now that you've mastered building these intricate, automated workflows, the next frontier is managing Claude's performance on very large, long-running tasks. In our next module, we'll dive into "Advanced Prompting & Large-Scale Refactoring," starting with how to use context management strategies like the /compact command to handle complex refactoring jobs that might otherwise exceed the context window.