Skip to content
Lesson 19 of 22

Advanced Vibe Coding Techniques

18 min read

Beyond the Basics

You have built real projects --- a landing page, a full-stack SaaS app, a public API, and AI-powered features. You know how to prompt, iterate, debug, and ship. But there is another level.

The techniques in this lesson are not about building any single feature. They are about building your entire development workflow. MCP servers connect your AI to external tools and data. Plan Mode helps you tackle complex, multi-file features without losing coherence. Custom slash commands let you automate repetitive tasks. Subagents divide and conquer. Hooks run scripts automatically in response to events.

These techniques compound over time. Each one saves minutes per day. Combined, they save hours per week. Over months of building, they change what is possible for a solo developer or a small team.

MCP Servers Deep Dive

What MCP Is

MCP stands for Model Context Protocol. It is an open standard that lets AI tools communicate with external services and data sources through a unified interface. Think of it as a universal adapter: instead of building custom integrations for every tool, you connect through MCP and everything speaks the same language.

Before MCP, if you wanted Claude Code to interact with your database, you had to manually copy data into the conversation. With MCP, Claude Code connects directly to your database, reads schemas, runs queries, and uses the results in context --- all without you copying and pasting anything.

MCP Architecture

The architecture is straightforward:

  • MCP Client: The AI tool (Claude Code, Cursor, etc.) that initiates requests
  • MCP Server: A small program that exposes tools, resources, and prompts to the client
  • Tools: Functions the AI can call (e.g., query a database, create a GitHub issue, send a Slack message)
  • Resources: Data the AI can read (e.g., database schema, file contents, API documentation)
  • Prompts: Pre-defined prompt templates the server provides

The client discovers what the server offers, and the AI decides when and how to use those capabilities based on the conversation context.

Pre-Built MCP Servers

The MCP ecosystem already has servers for common tools:

| Server | What It Does | |--------|-------------| | Database | Query PostgreSQL, MySQL, SQLite directly | | Filesystem | Read and write files outside the project directory | | GitHub | Create issues, review PRs, manage repositories | | Notion | Read and update Notion pages and databases | | Slack | Send messages, read channels | | Figma | Read design files, extract component specs | | Memory | Persistent knowledge graph across sessions | | Playwright | Automate browser interactions |

Installing a pre-built server typically means adding a configuration entry. For example, adding the GitHub MCP server to Claude Code:

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_your_token_here"
      }
    }
  }
}

On Windows, you may need to use cmd /c npx as the command to avoid shell issues.

Once configured, Claude Code can create GitHub issues, review pull request diffs, search repositories, and more --- all through natural conversation.

Building Your Own MCP Server

Pre-built servers cover common tools, but your project has unique needs. Building a custom MCP server lets you give your AI direct access to your specific data and operations.

When to build one:

  • You have a custom database schema the AI needs to understand
  • You want the AI to interact with an internal API
  • You need specialized tools not covered by existing servers

The minimal server structure:

// mcp-server/index.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = new Server(
  {
    name: "flowtask-mcp",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
      resources: {},
    },
  }
);

// Register a tool
server.setRequestHandler("tools/list", async () => ({
  tools: [
    {
      name: "get_tasks",
      description: "Get tasks from FlowTask, optionally filtered by status",
      inputSchema: {
        type: "object",
        properties: {
          status: {
            type: "string",
            enum: ["TODO", "IN_PROGRESS", "DONE"],
            description: "Filter by task status",
          },
        },
      },
    },
  ],
}));

// Handle tool calls
server.setRequestHandler("tools/call", async (request) => {
  if (request.params.name === "get_tasks") {
    const { status } = request.params.arguments || {};
    const tasks = await fetchTasksFromDatabase(status);
    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(tasks, null, 2),
        },
      ],
    };
  }
});

// Start the server
const transport = new StdioServerTransport();
await server.connect(transport);

Example: FlowTask MCP Server

Let us build an MCP server that connects Claude Code directly to FlowTask's database:

Build an MCP server for FlowTask that exposes:

Tools:
- get_tasks: List tasks with optional status/priority filters
- create_task: Create a new task in a specified project
- update_task_status: Change a task's status
- get_project_summary: Get task counts and status breakdown for a project

Resources:
- database_schema: The current Prisma schema
- task_statistics: Real-time stats (total, completed today, overdue)

Package it as a standalone Node.js script that connects via stdio.

With this server running, you can tell Claude Code things like "show me all overdue tasks in the Marketing project" and it will query your actual database and respond with real data.

Configuring MCP in Claude Code

MCP servers are configured in .claude/settings.json at the project level or in your global settings:

{
  "mcpServers": {
    "flowtask": {
      "command": "node",
      "args": ["./mcp-server/index.js"],
      "env": {
        "DATABASE_URL": "file:./prisma/dev.db"
      }
    },
    "github": {
      "command": "cmd",
      "args": ["/c", "npx", "-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
      }
    }
  }
}

Each server runs as a separate process. Claude Code communicates with them over standard input/output. You can run multiple servers simultaneously, and the AI will use whichever tools are relevant to the current task.

Plan Mode Mastery

Plan Mode is Claude Code's feature for tackling complex, multi-step work. Instead of executing immediately, the AI first creates a detailed plan, you review and adjust it, and then it executes step by step.

When to Use Plan Mode

Plan Mode shines for:

  • Complex features that touch many files (new authentication system, major refactors)
  • Architectural decisions where you want to think through the approach before committing
  • Multi-step migrations (changing database schema, updating all affected code, running tests)
  • Features you have not built before where planning reduces the risk of going down the wrong path

For small, well-defined changes (fix a typo, add a CSS class, create a simple component), skip Plan Mode and prompt directly. It adds overhead that is not worth it for straightforward tasks.

Writing Effective Plan Prompts

A good plan prompt defines the goal, constraints, and scope:

Plan a notification system for FlowTask.

Requirements:
- In-app notifications (bell icon with unread count in the nav bar)
- Email notifications for task assignments and due date reminders
- User preferences for which notifications they receive
- Notification center page showing all notifications with read/unread state

Constraints:
- Use the existing Resend integration for emails
- Store notifications in the database (new Notification model)
- Don't add any new external dependencies
- Keep the notification check efficient (don't query on every page load)

Plan this feature before implementing. Show me the database changes,
new files to create, existing files to modify, and the implementation order.

Reviewing and Modifying Plans

The AI will produce a step-by-step plan. Review it carefully:

  1. Does the database schema make sense? Check field types, indexes, and relationships.
  2. Is the implementation order logical? Database changes should come before code that uses them.
  3. Are there missing steps? The plan might omit migration commands, test updates, or UI adjustments.
  4. Is the scope right? If the plan is too ambitious, ask to reduce scope. If it misses something, add it.

Modify the plan before execution:

Good plan, but make these changes:
- Skip email notifications for now, focus on in-app only
- Use Server-Sent Events instead of polling for real-time updates
- Add the notification preference toggles to the existing settings page
  instead of creating a new page

Iterating on Plans

Plans are living documents. After the first phase of implementation, you might realize the approach needs adjustment:

The notification system is working for in-app notifications. Now let's
plan phase 2: adding email notification support. We already have the
Notification model and the preference settings. Plan the email delivery
layer using the existing Resend integration.

This iterative planning approach --- plan, execute, evaluate, re-plan --- keeps complex features on track without over-planning up front.

Multi-File Refactoring Patterns

Refactoring is where vibe coding truly shines over traditional development. Changes that would take hours of careful, manual editing across dozens of files become single prompts.

Renaming Across the Codebase

Rename the "Task" model to "WorkItem" throughout the entire codebase.
This includes:
- Prisma schema (model name, relation names)
- All TypeScript types and interfaces
- All import statements
- All variable names that reference Task/task
- All API routes and endpoints
- All UI components that display task data
- All test files

Run the Prisma migration after updating the schema. Run all tests after
the rename to verify nothing broke.

The AI will methodically work through every file. This is a refactoring that would take a human developer an hour or more of tedious find-and-replace, with the constant risk of missing something.

Extracting Shared Utilities

I notice we have duplicated date formatting logic in 5 different components.
Extract a shared utility:

1. Find all places where dates are formatted for display
2. Create a lib/format.ts file with formatDate, formatRelativeTime, and
   formatDateRange functions
3. Replace all duplicated date formatting with calls to these utilities
4. Make sure the output format stays identical

The Safety Protocol

For any refactoring, follow this sequence:

  1. Commit the current working state
  2. Refactor using AI prompts
  3. Test to verify nothing broke
  4. Commit the refactored code

If tests fail after the refactoring, you can always revert to the pre-refactoring commit. This safety net makes aggressive refactoring risk-free:

git add . && git commit -m "chore: snapshot before refactoring"
# ... do the refactoring ...
npm run test
# If tests pass:
git add . && git commit -m "refactor: extract date formatting utilities"
# If tests fail:
git checkout .  # Revert everything

Migrating Patterns

Sometimes you want to change an entire architectural pattern:

Migrate all data fetching from client-side useEffect + fetch to Next.js
Server Components with server-side data fetching. Go file by file:

1. Identify all components that fetch data in useEffect
2. For each one, move the data fetching to the page-level Server Component
3. Pass the data as props to Client Components that need interactivity
4. Remove the loading states that are no longer needed (data arrives
   with the initial HTML)
5. Keep Client Components only for interactive elements

Process one file at a time and test between each migration.

Custom Slash Commands

Custom slash commands automate repetitive prompts. Instead of typing the same instructions every time, you create a command file and invoke it with a short name.

Creating Commands

Create a .claude/commands/ directory in your project:

mkdir -p .claude/commands

Each command is a Markdown file. The filename (minus extension) becomes the command name.

Example: /deploy-check

<!-- .claude/commands/deploy-check.md -->
Before deploying, check the following:

1. Run `npm run build` and verify it succeeds with no errors
2. Run `npm run test` and verify all tests pass
3. Check for any TypeScript errors with `npx tsc --noEmit`
4. Check for linting issues with `npm run lint`
5. Verify no secrets are committed (search for API keys, passwords, tokens)
6. Check that .env.example is up to date with all required variables

Report a summary: what passed, what failed, and whether it's safe to deploy.

Now in Claude Code, you type /deploy-check and all those steps execute automatically.

Example: /add-feature

<!-- .claude/commands/add-feature.md -->
Add a new feature to the application. The feature is: $ARGUMENTS

Steps:
1. Plan the feature (database changes, new files, modified files)
2. Present the plan for review
3. After approval, implement step by step
4. Write tests for the new feature
5. Run all tests to ensure nothing broke
6. Create a git commit with a descriptive message

Follow the project conventions in CLAUDE.md.

The $ARGUMENTS placeholder gets replaced with whatever you type after the command name: /add-feature user profile avatars.

Example: /review

<!-- .claude/commands/review.md -->
Review the current git diff for:

1. **Bugs**: Logic errors, off-by-one errors, null pointer risks
2. **Security**: SQL injection, XSS, exposed secrets, missing auth checks
3. **Performance**: N+1 queries, unnecessary re-renders, missing indexes
4. **Style**: Consistency with project conventions, naming, formatting
5. **Types**: Missing or incorrect TypeScript types

For each issue found, explain the problem and suggest a fix. Rate the
overall quality: Ship It, Needs Minor Changes, or Needs Major Changes.

Example: /test-all

<!-- .claude/commands/test-all.md -->
Run the complete test suite and analyze results:

1. Run `npm run test` and capture output
2. If any tests fail, analyze the failures
3. For each failure, determine if it's a real bug or a flaky test
4. Suggest fixes for real bugs
5. Report: total tests, passed, failed, and execution time

Subagents and Parallel Execution

Subagents are independent AI agents that handle subtasks while you continue working on the main task. They are like delegating work to a team member who comes back with results.

What Subagents Are

When you dispatch a subagent from Claude Code, it runs as a separate process with its own context. It can read files, run commands, and produce output --- all independently of your main session. You get the results when the subagent finishes.

When to Use Subagents

Subagents work best for:

  • Independent tasks: Writing tests while you implement features
  • Research: Analyzing a library's documentation while you build with it
  • Parallel work: Generating database seeds while you build the UI
  • Code review: Having a subagent review your changes while you keep building

Dispatching Work

In Claude Code, you can ask for work to be done in parallel:

While I work on the task creation form, use a subagent to:
1. Write comprehensive tests for the existing project CRUD operations
2. Cover create, read, update, delete, and edge cases
3. Use vitest with the test database

I'll review the tests when they're ready.

The subagent works independently. You do not need to wait for it. When it finishes, you see the results and can integrate or adjust them.

Reviewing Subagent Output

Always review subagent output before committing it. The subagent works with limited context compared to your main session, so it might:

  • Use outdated patterns that you changed earlier in the session
  • Miss project-specific conventions
  • Make assumptions about file structure that are incorrect

Treat subagent output like a pull request from a team member: review it, suggest changes if needed, and merge when it meets your standards.

Hooks for Automation

Hooks are scripts that run automatically when specific events happen in Claude Code. They bridge the gap between AI-assisted development and your local toolchain.

What Hooks Are

A hook is a script (shell, Python, Node.js, or any executable) that Claude Code triggers at specific points in its workflow. You configure them in .claude/settings.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "command": "npx prettier --write $FILE_PATH"
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Bash",
        "command": "echo 'Running command: $TOOL_INPUT'"
      }
    ],
    "Stop": [
      {
        "command": "npm run lint"
      }
    ]
  }
}

Hook Types

| Hook | When It Runs | Use Case | |------|-------------|----------| | PreToolUse | Before the AI uses a tool | Validation, logging | | PostToolUse | After the AI uses a tool | Formatting, linting | | Stop | When the AI finishes its response | Final checks | | SessionStart | When a new session begins | Environment setup |

Example: Auto-Format After Every Edit

Every time Claude Code edits or writes a file, Prettier runs automatically:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "command": "npx prettier --write $FILE_PATH"
      }
    ]
  }
}

This means you never need to ask the AI to format code --- it happens automatically. The code in your repository is always formatted correctly.

Example: Lint Before Every Commit

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash(.*git commit.*)",
        "command": "npm run lint"
      }
    ]
  }
}

If the linter finds errors, the hook fails, and the commit is blocked. This is your safety net against committing code with obvious issues.

Example: Notify on Task Completion

{
  "hooks": {
    "Stop": [
      {
        "command": "node .claude/scripts/notify.js"
      }
    ]
  }
}

The notify.js script could send a desktop notification, post to Slack, or log the completion. This is especially useful for long-running tasks where you have moved on to something else while waiting.

Building Custom Hook Scripts

Hooks can be more sophisticated than one-liners. Create a scripts directory:

mkdir -p .claude/scripts

A validation hook that checks for common security issues:

// .claude/scripts/security-check.js
const fs = require("fs");
const path = process.argv[2]; // File path passed by the hook

if (!path) process.exit(0);

const content = fs.readFileSync(path, "utf-8");

const patterns = [
  /password\s*=\s*["'][^"']+["']/i,
  /api[_-]?key\s*=\s*["'][^"']+["']/i,
  /secret\s*=\s*["'][^"']+["']/i,
];

for (const pattern of patterns) {
  if (pattern.test(content)) {
    console.error(`WARNING: Possible secret detected in ${path}`);
    process.exit(1);
  }
}

Building Your Personal AI Development Environment

Everything in this lesson --- MCP servers, Plan Mode, custom commands, subagents, hooks --- comes together to form your personal development environment. This is not a one-time setup. It evolves with every project.

Global CLAUDE.md

Your global CLAUDE.md file lives in your home directory and applies to every project. It contains your personal preferences:

# Global Preferences

## Language
- Code comments in English
- Variable names in English
- Commit messages in English

## Code Style
- Use ES modules (import/export)
- Prefer TypeScript with strict mode
- Use async/await
- Destructure imports

## Workflow
- Always run tests after changes
- Prefer small, focused commits
- Use conventional commits

This file means you never need to re-explain your preferences. Every Claude Code session starts with this context.

Project-Specific Configurations

Each project gets its own CLAUDE.md with architecture details, conventions, and current state. Update it as the project evolves:

## Current State
- Authentication: complete (NextAuth + GitHub)
- Projects CRUD: complete
- Tasks CRUD: complete with kanban view
- API: v1 endpoints complete, documentation pending
- Next: notification system

MCP Server Collection

Over time, you build a collection of MCP servers for your common tools:

  • Database server for your current project
  • GitHub server for repository management
  • Notion server for project documentation
  • Memory server for persistent context

Each project's settings.json references the servers it needs. New projects can reuse servers from previous ones.

Custom Commands Library

Build commands for tasks you repeat across projects:

.claude/commands/
  deploy-check.md      # Pre-deployment verification
  add-feature.md       # Feature planning and implementation
  review.md            # Code review
  test-all.md          # Run and analyze tests
  security-audit.md    # Check for security issues
  performance-check.md # Analyze performance
  db-migrate.md        # Database migration workflow

Share your commands library across projects by keeping them in a git repository and symlinking them into each project.

Hooks for Your Workflow

Your hook configuration becomes standardized:

  • PostToolUse: auto-format with Prettier
  • PreToolUse: log commands for debugging
  • Stop: run linter, notify on completion
  • SessionStart: check environment variables, verify database connection

Making Every Session Faster Than the Last

The compound effect is powerful. Your first project with Claude Code might take days. Your fifth takes hours. Your twentieth takes an afternoon. Not because the projects are simpler, but because:

  1. Your CLAUDE.md files are battle-tested and precise
  2. Your MCP servers give the AI direct access to the tools it needs
  3. Your custom commands eliminate repetitive prompt writing
  4. Your hooks catch mistakes automatically
  5. Your plan prompts are refined from dozens of iterations

This is the meta-skill of vibe coding: building the environment that makes building faster. Every hour you invest in your setup pays dividends across every future project.

What's Next

You have now mastered the advanced techniques that separate casual AI users from power users. MCP servers, Plan Mode, custom commands, subagents, and hooks give you a development workflow that is both faster and more reliable than traditional methods.

In Module V, we shift from building to launching. We will cover taking your project to production with proper infrastructure, optimizing for SEO and growth, and monetizing your product. You have the skills to build anything --- now let us learn to ship it to the world.