Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Troubleshooting Integrated Workflow Communication

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.

Claude Code MCP Server Integration Workflow
This diagram shows the core interaction: Claude Code offloads a specific task (like logging) to an MCP server, which executes it and returns a result. Our FastAPI app will play the role of this 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

  1. 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.py and app/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.
  2. 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 Edit tool on app/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.

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:

  1. Problem: The /new-model command isn't found.

    • Check: Is the SKILL.md file in the right place (.claude/skills/new-model/SKILL.md)?
    • Check: Is there a syntax error in the frontmatter YAML?
  2. Problem: Claude says it can't use the mcp__audit__log tool.

    • Check MCP Status: Run /mcp in the session. Does it show the audit server as connected?
    • Check Server Logs: Look at the terminal where you launched Claude Code. Did the audit_server.py start? Are there any errors from FastAPI or Uvicorn?
    • Check Permissions: The skill's frontmatter includes mcp__audit__log in allowed-tools. If you were running this without a skill, you might need to grant permission with /permissions.
  3. Problem: The hook doesn't block the edit on the protected file.

    • Check Hook Registration: Run /hooks or claude --print-hooks. Is your PreToolUse hook listed with the correct matcher and command?
    • Check Hook Script: Is the script executable (chmod +x)? Does it have the correct path in settings.json?
    • Test the Hook Manually: You can simulate Claude's input to test your script directly, as suggested in the claude-code-guide.
      echo '{"tool_name":"Edit","tool_input":{"file_path":"app/db/base.py"}}' | .claude/hooks/protect-base-schema.sh
      
      
      
      # Check the exit code
      echo $? 
      
      This should print your error message and the exit code should be 2.
  4. Problem: You can't see what's happening.

    • Use Verbose Mode: Press Ctrl+O in 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.

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 2 is 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.

Can't find a good explanation? Sign up and we'll make it for you

Sign up