Chapter 21: [Tool] Architect's Debugging Weapon: next-devtools-mcp
Welcome to the final chapter of this book. In Chapter 20, we confronted the most headache-inducing traps in Next.js 15+ architecture, especially React Server Components (RSC) caching issues and various anti-patterns. You might be thinking: 'I understand the theory, but when my app actually has cache not refreshing or data corruption, how do I real-time see what's happening inside the 'black box' like Python's debugger (PDB)...'
Chapter 21: [Tool] Architect's Debugging Weapon: next-devtools-mcp
Welcome to the final chapter of this book. In Chapter 20, we confronted the most headache-inducing traps in Next.js 15+ architecture, especially React Server Components (RSC) caching issues and various anti-patterns. You might be thinking: "I understand the theory, but when my app actually has cache not refreshing or data corruption, how do I real-time see what's happening inside the 'black box' like Python's debugger (PDB)?"
For Python developers, we're used to having powerful backend debugging tools. But in Next.js 15+'s complex model spanning server and client, debugging becomes extremely difficult. Errors can occur in four places: client, server components (RSC), Server Actions, or API routes.
In this chapter, we'll introduce a "ultimate debugging weapon" built specifically for this scenario: next-devtools-mcp. This is the core tool of the claude-next-devtools-mcp repository that this book relies on. It will transform your AI assistant (like Claude or Cursor) from a "code generator" into a "real-time application diagnostic engineer".
21.1. Introduction: What is next-devtools-mcp (Based on README.md)
According to the project's README.md, next-devtools-mcp is a Model Context Protocol (MCP) server.
Its core function isn't what it does itself, but rather its role as a "bridge" (README.md: "bridge MCP server"). It connects three key parts:
- AI coding assistant (e.g., Claude in your IDE): You interact with it through a chat interface.
next-devtools-mcpserver (this book's tool): It runs in the background, understanding AI requests.- Your running Next.js development server: This is where the magic happens.
As confirmed by the src/_internal/nextjs-runtime-manager.ts file, starting from Next.js 16, the development server (next dev) enables a built-in MCP endpoint at the /_next/mcp path by default. The next-devtools-mcp tool can automatically discover and connect to this endpoint, giving the AI assistant real-time "read" access to your application.
In short: It allows your AI assistant to directly talk to your locally running next dev server.
The README.md and src/index.ts files show it provides a series of tools, including:
nextjs_runtime: Real-time diagnosis of application state, reading errors and logs. (Chapter focus)nextjs_docs: Query Next.js official documentation and knowledge base.browser_eval: Run browser automation tests via Playwright.upgrade_nextjs_16/enable_cache_components: Specialized tools for project upgrades and migrations.
This chapter, we'll focus on nextjs_runtime, the most powerful tool for daily debugging as an architect.
21.2. Practice: Diagnosing Applications with nextjs_runtime
According to the metadata.description in the src/tools/nextjs-runtime.ts file, this tool's core value is for use "before implementing any changes" and "for diagnosing and investigating issues".
It allows us to inspect RSC payloads, cache states, and Server Actions in real-time without manually adding console.log to code.
The inputSchema of the nextjs_runtime tool provides three core actions:
discover_servers: Find all Next.js development servers running locally.list_tools: List all available diagnostic tools exposed by the development server (via/_next/mcp).call_tool: Call the above diagnostic tools.
Diagnosis Workflow 1: Checking RSC Payloads and Route Structure
Suppose our SAAS application is running, and we want to know what the dashboard page's route structure and RSC payload look like.
Step 1: Discover servers We (or AI) first call:
nextjs_runtime({
"action": "discover_servers"
})Simulated response:
{
"success": true,
"count": 1,
"servers": [{
"port": 3000,
"pid": 12345,
"url": "http://localhost:3000",
"mcpEndpoint": "http://localhost:3000/_next/mcp"
}]
}Step 2: List available diagnostic tools Now we know the server is on port 3000, let's see what it can do:
nextjs_runtime({
"action": "list_tools",
"port": 3000
})Simulated response (based on README.md and nextjs-runtime.ts descriptions):
{
"success": true,
"port": 3000,
"tools": [
{ "name": "get_errors", "description": "Get current build, runtime, and type errors" },
{ "name": "get_logs", "description": "Get development log file paths" },
{ "name": "get_page_metadata", "description": "Query app's routes, pages, and component metadata" },
{ "name": "get_server_action_by_id", "description": "Find Server Action by ID" }
]
}Step 3: Call tool to get RSC payload and cache state
We're most interested in get_page_metadata (page metadata).
nextjs_runtime({
"action": "call_tool",
"port": 3000,
"toolName": "get_page_metadata",
"args": { "route": "/dashboard" }
})Simulated response (this is the "RSC payload and cache state" we need):
{
"success": true,
"result": {
"route": "/dashboard",
"cacheStatus": "STALE", // Cache state
"lastValidated": "2025-11-14T07:10:00Z",
"componentTree": [ // RSC payload (component tree)
{
"component": "Page",
"file": "app/[locale]/dashboard/page.tsx",
"children": [
{ "component": "WelcomeHeader", "cacheStatus": "VALIDATED" },
{ "component": "StatsDisplay", "cacheStatus": "STALE" },
{ "component": "ActivityFeed", "cacheStatus": "DYNAMIC" }
]
}
]
}
}With this response, we immediately know (without looking at code) that the StatsDisplay component's cache is "STALE" while ActivityFeed is "DYNAMIC".
Diagnosis Workflow 2: Tracing Server Actions
Suppose a Server Action fails, but the browser only gives a vague summary ID. The get_server_action_by_id tool is made for this.
nextjs_runtime({
"action": "call_tool",
"port": 3000,
"toolName": "get_server_action_by_id",
"args": { "id": "a1b2c3d4e5f6..." }
})Response:
{
"success": true,
"result": {
"id": "a1b2c3d4e5f6...",
"sourceFile": "app/actions/payment.actions.ts",
"functionName": "createCheckoutSession"
}
}This tool immediately tells us the error's source is in the createCheckoutSession function in the payment.actions.ts file.
21.3. Case Study: Debugging "Why isn't my cache refreshing?"
Let's combine Chapter 20's traps with next-devtools-mcp to solve a real architectural problem.
Scenario:
Our SAAS has a app/products/[id]/page.tsx page that uses fetch with tags: ['products'] to display product information. We also have a background Server Action, updateProduct, that calls revalidateTag('products') after updating the database to refresh cache.
Problem: Developers report that after calling updateProduct, the product page still shows old data.
Debugging Steps:
Step 1: Check code (traditional approach)
We open app/products/[id]/page.tsx and confirm fetch includes next: { tags: ['products'] }. Code is correct.
We open app/admin/actions.ts and look at the updateProduct function:
// app/admin/actions.ts
'use server';
import { db } from '@/db';
import { revalidateTag } from 'next/cache';
export async function updateProduct(productId: string, newData: any) {
await db.update(...); // Database update
revalidateTag('products'); // [!] Potential trap!
}Code also looks "correct". So where's the problem?
Step 2: Real-time diagnosis with next-devtools-mcp
We (or AI) will use nextjs_runtime to see what the Next.js server really thinks.
AI Call 1: Check page cache state First, we visit the product page in the browser, then run:
nextjs_runtime({
"action": "call_tool",
"port": 3000,
"toolName": "get_page_metadata",
"args": { "route": "/products/123" }
})Response: { "route": "/products/123", "cacheStatus": "VALIDATED", "tags": ["products"] }
This confirms the page is indeed cached and associated with the "products" tag.
AI Call 2: Monitor real-time logs
Now, we have the developer click the backend's "Update Product" button (which triggers the updateProduct Server Action). Meanwhile, we (AI) run:
nextjs_runtime({
"action": "call_tool",
"port": 3000,
"toolName": "get_logs",
"args": { "lines": 50 }
})In the returned real-time logs, we find the key clue:
...
(fast-refresh) rebuilding...
(cache) Server Action 'updateProduct' triggered.
(cache) revalidateTag('products') called.
(cache) WARNING: revalidateTag('products') in a Server Action is deprecated.
This schedules a background revalidation.
If you want read-your-own-writes consistency, use updateTag('products') instead.
...Step 3: Identify and fix problem
The log warning hits the nail on the head! revalidateTag (mentioned in Chapter 20 traps) doesn't immediately refresh cache—it only schedules a background refresh. This means users might still see old data when refreshing (before background refresh completes).
Based on knowledge from src/resources/(cache-components)/07-cache-invalidation.md (updateTag() for immediate invalidation) and the log's suggestion, the correct fix is to use updateTag for "read-your-own-writes" consistency.
Fix:
// app/admin/actions.ts
'use server';
import { db } from '@/db';
// Import updateTag instead of revalidateTag
import { updateTag } from 'next/cache';
export async function updateProduct(productId: string, newData: any) {
await db.update(...);
updateTag('products'); // [✓] Fixed!
}Step 4: Real-time verification of fix We have the developer click "Update Product" button again.
AI Call 3: Check page cache state again
nextjs_runtime({
"action": "call_tool",
"port": 3000,
"toolName": "get_page_metadata",
"args": { "route": "/products/123" }
})Response: { "route": "/products/123", "cacheStatus": "BYPASSED_UNTIL_REVALIDATED", ... }
(or cacheStatus: "VALIDATED", "lastValidated": "...(now)...")
This response confirms cache is immediately marked invalid (or already refreshed). Developer refreshes browser and sees latest data. Problem solved.
Chapter 21 Summary
In this chapter, we added the final and most critical tool to our architect's toolbox: next-devtools-mcp.
- What it is: We learned that
next-devtools-mcpis a "bridge" MCP server that allows our AI assistant to directly communicate with our locally running Next.js 16+ development server (via/_next/mcpendpoint). - Core Tool
nextjs_runtime: We practiced the three core actions of thenextjs_runtimetool:discover_servers,list_tools, andcall_tool. - Real-time Diagnosis: We learned how to call
get_page_metadatato check RSC payloads and cache states, and how to useget_server_action_by_idto locate Server Action source files. - Debugging Case Study: We demonstrated through a real cache-not-refreshing case how to use
get_logsto diagnose therevalidateTagvsupdateTagconfusion issue (based on knowledge from07-cache-invalidation.md) and verify the fix in real-time.
This book started with Python's mindset, taking us through the full-stack landscape of TypeScript, RSC, Server Actions, Drizzle, Stripe, AI SDK, and DevOps. Now, with next-devtools-mcp, we've not only mastered the theory of building modern SAAS, but gained the "X-ray vision" to diagnose and debug this complex system in real-time.
Categories
More Posts
Chapter 20: Architect's Pitfall Avoidance Guide
Welcome to the final part of this book. In the past 19 chapters, we've gone through a mindset shift from Python backend to modern JS full-stack together. We've built a feature-complete, observable, testable, safely deployable AI SAAS.
Chapter 15: Code Quality (Biome) and Content (Fumadocs)
In Chapter 14, we established a solid CI/CD pipeline that acts as the 'gatekeeper' for our SAAS application. One of the core responsibilities of this gatekeeper is running pnpm lint and pnpm typecheck. But what's actually working behind these commands?
Chapter 13: SAAS Operations: Email and Notifications
A SAAS application cannot operate long-term if it only 'takes in' without 'giving back'. When users perform actions on your platform, the platform must provide feedback in some way. After your application launches and runs, you also need a way to reach your users, whether to notify them of new features or provide help when they encounter problems.