This commit is contained in:
Kismet Hasanaj
2026-05-02 20:07:02 +02:00
parent ce8672e283
commit 34dc9aec52
9428 changed files with 1733330 additions and 0 deletions
+39
View File
@@ -0,0 +1,39 @@
import { getOrCreateMcpServer } from './get-or-create-mcp-server';
import { parseBody } from '../api-utils/node/parse-body';
import { StreamableHTTPServerTransport } from 'next/dist/compiled/@modelcontextprotocol/sdk/server/streamableHttp';
export function getMcpMiddleware(options) {
return async function(req, res, next) {
const { pathname } = new URL(req.url || '', 'http://n');
if (!pathname.startsWith('/_next/mcp')) {
return next();
}
const mcpServer = getOrCreateMcpServer(options);
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined
});
try {
res.on('close', ()=>{
transport.close();
});
await mcpServer.connect(transport);
const parsedBody = await parseBody(req, 1024 * 1024) // 1MB limit
;
await transport.handleRequest(req, res, parsedBody);
} catch (error) {
if (!res.headersSent) {
res.statusCode = 500;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Internal server error'
},
id: null
}));
}
}
};
}
//# sourceMappingURL=get-mcp-middleware.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/server/mcp/get-mcp-middleware.ts"],"sourcesContent":["import type { ServerResponse, IncomingMessage } from 'http'\nimport {\n getOrCreateMcpServer,\n type McpServerOptions,\n} from './get-or-create-mcp-server'\nimport { parseBody } from '../api-utils/node/parse-body'\nimport { StreamableHTTPServerTransport } from 'next/dist/compiled/@modelcontextprotocol/sdk/server/streamableHttp'\n\nexport function getMcpMiddleware(options: McpServerOptions) {\n return async function (\n req: IncomingMessage,\n res: ServerResponse,\n next: () => void\n ): Promise<void> {\n const { pathname } = new URL(req.url || '', 'http://n')\n if (!pathname.startsWith('/_next/mcp')) {\n return next()\n }\n const mcpServer = getOrCreateMcpServer(options)\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n })\n try {\n res.on('close', () => {\n transport.close()\n })\n await mcpServer.connect(transport)\n const parsedBody = await parseBody(req, 1024 * 1024) // 1MB limit\n await transport.handleRequest(req, res, parsedBody)\n } catch (error) {\n if (!res.headersSent) {\n res.statusCode = 500\n res.setHeader('Content-Type', 'application/json; charset=utf-8')\n res.end(\n JSON.stringify({\n jsonrpc: '2.0',\n error: { code: -32000, message: 'Internal server error' },\n id: null,\n })\n )\n }\n }\n }\n}\n"],"names":["getOrCreateMcpServer","parseBody","StreamableHTTPServerTransport","getMcpMiddleware","options","req","res","next","pathname","URL","url","startsWith","mcpServer","transport","sessionIdGenerator","undefined","on","close","connect","parsedBody","handleRequest","error","headersSent","statusCode","setHeader","end","JSON","stringify","jsonrpc","code","message","id"],"mappings":"AACA,SACEA,oBAAoB,QAEf,6BAA4B;AACnC,SAASC,SAAS,QAAQ,+BAA8B;AACxD,SAASC,6BAA6B,QAAQ,qEAAoE;AAElH,OAAO,SAASC,iBAAiBC,OAAyB;IACxD,OAAO,eACLC,GAAoB,EACpBC,GAAmB,EACnBC,IAAgB;QAEhB,MAAM,EAAEC,QAAQ,EAAE,GAAG,IAAIC,IAAIJ,IAAIK,GAAG,IAAI,IAAI;QAC5C,IAAI,CAACF,SAASG,UAAU,CAAC,eAAe;YACtC,OAAOJ;QACT;QACA,MAAMK,YAAYZ,qBAAqBI;QACvC,MAAMS,YAAY,IAAIX,8BAA8B;YAClDY,oBAAoBC;QACtB;QACA,IAAI;YACFT,IAAIU,EAAE,CAAC,SAAS;gBACdH,UAAUI,KAAK;YACjB;YACA,MAAML,UAAUM,OAAO,CAACL;YACxB,MAAMM,aAAa,MAAMlB,UAAUI,KAAK,OAAO,MAAM,YAAY;;YACjE,MAAMQ,UAAUO,aAAa,CAACf,KAAKC,KAAKa;QAC1C,EAAE,OAAOE,OAAO;YACd,IAAI,CAACf,IAAIgB,WAAW,EAAE;gBACpBhB,IAAIiB,UAAU,GAAG;gBACjBjB,IAAIkB,SAAS,CAAC,gBAAgB;gBAC9BlB,IAAImB,GAAG,CACLC,KAAKC,SAAS,CAAC;oBACbC,SAAS;oBACTP,OAAO;wBAAEQ,MAAM,CAAC;wBAAOC,SAAS;oBAAwB;oBACxDC,IAAI;gBACN;YAEJ;QACF;IACF;AACF","ignoreList":[0]}
+31
View File
@@ -0,0 +1,31 @@
import { McpServer } from 'next/dist/compiled/@modelcontextprotocol/sdk/server/mcp';
import { registerGetProjectMetadataTool } from './tools/get-project-metadata';
import { registerGetErrorsTool } from './tools/get-errors';
import { registerGetPageMetadataTool } from './tools/get-page-metadata';
import { registerGetLogsTool } from './tools/get-logs';
import { registerGetActionByIdTool } from './tools/get-server-action-by-id';
import { registerGetRoutesTool } from './tools/get-routes';
let mcpServer;
export const getOrCreateMcpServer = (options)=>{
if (mcpServer) {
return mcpServer;
}
mcpServer = new McpServer({
name: 'Next.js MCP Server',
version: '0.2.0'
});
registerGetProjectMetadataTool(mcpServer, options.projectPath, options.getDevServerUrl);
registerGetErrorsTool(mcpServer, options.sendHmrMessage, options.getActiveConnectionCount);
registerGetPageMetadataTool(mcpServer, options.sendHmrMessage, options.getActiveConnectionCount);
registerGetLogsTool(mcpServer, options.distDir);
registerGetActionByIdTool(mcpServer, options.distDir);
registerGetRoutesTool(mcpServer, {
projectPath: options.projectPath,
nextConfig: options.nextConfig,
pagesDir: options.pagesDir,
appDir: options.appDir
});
return mcpServer;
};
//# sourceMappingURL=get-or-create-mcp-server.js.map
@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/server/mcp/get-or-create-mcp-server.ts"],"sourcesContent":["import { McpServer } from 'next/dist/compiled/@modelcontextprotocol/sdk/server/mcp'\nimport { registerGetProjectMetadataTool } from './tools/get-project-metadata'\nimport { registerGetErrorsTool } from './tools/get-errors'\nimport { registerGetPageMetadataTool } from './tools/get-page-metadata'\nimport { registerGetLogsTool } from './tools/get-logs'\nimport { registerGetActionByIdTool } from './tools/get-server-action-by-id'\nimport { registerGetRoutesTool } from './tools/get-routes'\nimport type { HmrMessageSentToBrowser } from '../dev/hot-reloader-types'\nimport type { NextConfigComplete } from '../config-shared'\n\nexport interface McpServerOptions {\n projectPath: string\n distDir: string\n nextConfig: NextConfigComplete\n pagesDir: string | undefined\n appDir: string | undefined\n sendHmrMessage: (message: HmrMessageSentToBrowser) => void\n getActiveConnectionCount: () => number\n getDevServerUrl: () => string | undefined\n}\n\nlet mcpServer: McpServer | undefined\n\nexport const getOrCreateMcpServer = (options: McpServerOptions) => {\n if (mcpServer) {\n return mcpServer\n }\n\n mcpServer = new McpServer({\n name: 'Next.js MCP Server',\n version: '0.2.0',\n })\n\n registerGetProjectMetadataTool(\n mcpServer,\n options.projectPath,\n options.getDevServerUrl\n )\n registerGetErrorsTool(\n mcpServer,\n options.sendHmrMessage,\n options.getActiveConnectionCount\n )\n registerGetPageMetadataTool(\n mcpServer,\n options.sendHmrMessage,\n options.getActiveConnectionCount\n )\n registerGetLogsTool(mcpServer, options.distDir)\n registerGetActionByIdTool(mcpServer, options.distDir)\n registerGetRoutesTool(mcpServer, {\n projectPath: options.projectPath,\n nextConfig: options.nextConfig,\n pagesDir: options.pagesDir,\n appDir: options.appDir,\n })\n\n return mcpServer\n}\n"],"names":["McpServer","registerGetProjectMetadataTool","registerGetErrorsTool","registerGetPageMetadataTool","registerGetLogsTool","registerGetActionByIdTool","registerGetRoutesTool","mcpServer","getOrCreateMcpServer","options","name","version","projectPath","getDevServerUrl","sendHmrMessage","getActiveConnectionCount","distDir","nextConfig","pagesDir","appDir"],"mappings":"AAAA,SAASA,SAAS,QAAQ,0DAAyD;AACnF,SAASC,8BAA8B,QAAQ,+BAA8B;AAC7E,SAASC,qBAAqB,QAAQ,qBAAoB;AAC1D,SAASC,2BAA2B,QAAQ,4BAA2B;AACvE,SAASC,mBAAmB,QAAQ,mBAAkB;AACtD,SAASC,yBAAyB,QAAQ,kCAAiC;AAC3E,SAASC,qBAAqB,QAAQ,qBAAoB;AAe1D,IAAIC;AAEJ,OAAO,MAAMC,uBAAuB,CAACC;IACnC,IAAIF,WAAW;QACb,OAAOA;IACT;IAEAA,YAAY,IAAIP,UAAU;QACxBU,MAAM;QACNC,SAAS;IACX;IAEAV,+BACEM,WACAE,QAAQG,WAAW,EACnBH,QAAQI,eAAe;IAEzBX,sBACEK,WACAE,QAAQK,cAAc,EACtBL,QAAQM,wBAAwB;IAElCZ,4BACEI,WACAE,QAAQK,cAAc,EACtBL,QAAQM,wBAAwB;IAElCX,oBAAoBG,WAAWE,QAAQO,OAAO;IAC9CX,0BAA0BE,WAAWE,QAAQO,OAAO;IACpDV,sBAAsBC,WAAW;QAC/BK,aAAaH,QAAQG,WAAW;QAChCK,YAAYR,QAAQQ,UAAU;QAC9BC,UAAUT,QAAQS,QAAQ;QAC1BC,QAAQV,QAAQU,MAAM;IACxB;IAEA,OAAOZ;AACT,EAAC","ignoreList":[0]}
+59
View File
@@ -0,0 +1,59 @@
/**
* Telemetry tracker for MCP tool call usage.
* Tracks invocation counts for each MCP tool to be reported via telemetry.
*/ class McpTelemetryTracker {
/**
* Record a tool call invocation
*/ recordToolCall(toolName) {
const current = this.usageMap.get(toolName) || 0;
this.usageMap.set(toolName, current + 1);
}
/**
* Get all tool usages as an array
*/ getUsages() {
return Array.from(this.usageMap.entries()).map(([featureName, count])=>({
featureName,
invocationCount: count
}));
}
/**
* Reset all usage tracking
*/ reset() {
this.usageMap.clear();
}
/**
* Check if any tools have been called
*/ hasUsage() {
return this.usageMap.size > 0;
}
constructor(){
this.usageMap = new Map();
}
}
// Singleton instance
export const mcpTelemetryTracker = new McpTelemetryTracker();
/**
* Get MCP tool usage telemetry
*/ export function getMcpTelemetryUsage() {
return mcpTelemetryTracker.getUsages();
}
/**
* Reset MCP telemetry tracker
*/ export function resetMcpTelemetry() {
mcpTelemetryTracker.reset();
}
/**
* Record MCP telemetry usage to the telemetry instance
*/ export function recordMcpTelemetry(telemetry) {
const mcpUsages = getMcpTelemetryUsage();
if (mcpUsages.length === 0) {
return;
}
const { eventMcpToolUsage } = require('../../telemetry/events/build');
const events = eventMcpToolUsage(mcpUsages);
for (const event of events){
telemetry.record(event);
}
}
//# sourceMappingURL=mcp-telemetry-tracker.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/server/mcp/mcp-telemetry-tracker.ts"],"sourcesContent":["/**\n * Telemetry tracker for MCP tool call usage.\n * Tracks invocation counts for each MCP tool to be reported via telemetry.\n */\n\nimport type { McpToolName } from '../../telemetry/events/build'\n\nexport interface McpToolUsage {\n featureName: McpToolName\n invocationCount: number\n}\n\nclass McpTelemetryTracker {\n private usageMap = new Map<McpToolName, number>()\n\n /**\n * Record a tool call invocation\n */\n recordToolCall(toolName: McpToolName): void {\n const current = this.usageMap.get(toolName) || 0\n this.usageMap.set(toolName, current + 1)\n }\n\n /**\n * Get all tool usages as an array\n */\n getUsages(): McpToolUsage[] {\n return Array.from(this.usageMap.entries()).map(([featureName, count]) => ({\n featureName,\n invocationCount: count,\n }))\n }\n\n /**\n * Reset all usage tracking\n */\n reset(): void {\n this.usageMap.clear()\n }\n\n /**\n * Check if any tools have been called\n */\n hasUsage(): boolean {\n return this.usageMap.size > 0\n }\n}\n\n// Singleton instance\nexport const mcpTelemetryTracker = new McpTelemetryTracker()\n\n/**\n * Get MCP tool usage telemetry\n */\nexport function getMcpTelemetryUsage(): McpToolUsage[] {\n return mcpTelemetryTracker.getUsages()\n}\n\n/**\n * Reset MCP telemetry tracker\n */\nexport function resetMcpTelemetry(): void {\n mcpTelemetryTracker.reset()\n}\n\n/**\n * Record MCP telemetry usage to the telemetry instance\n */\nexport function recordMcpTelemetry(telemetry: {\n record: (event: any) => void\n}): void {\n const mcpUsages = getMcpTelemetryUsage()\n if (mcpUsages.length === 0) {\n return\n }\n\n const { eventMcpToolUsage } =\n require('../../telemetry/events/build') as typeof import('../../telemetry/events/build')\n const events = eventMcpToolUsage(mcpUsages)\n for (const event of events) {\n telemetry.record(event)\n }\n}\n"],"names":["McpTelemetryTracker","recordToolCall","toolName","current","usageMap","get","set","getUsages","Array","from","entries","map","featureName","count","invocationCount","reset","clear","hasUsage","size","Map","mcpTelemetryTracker","getMcpTelemetryUsage","resetMcpTelemetry","recordMcpTelemetry","telemetry","mcpUsages","length","eventMcpToolUsage","require","events","event","record"],"mappings":"AAAA;;;CAGC,GASD,MAAMA;IAGJ;;GAEC,GACDC,eAAeC,QAAqB,EAAQ;QAC1C,MAAMC,UAAU,IAAI,CAACC,QAAQ,CAACC,GAAG,CAACH,aAAa;QAC/C,IAAI,CAACE,QAAQ,CAACE,GAAG,CAACJ,UAAUC,UAAU;IACxC;IAEA;;GAEC,GACDI,YAA4B;QAC1B,OAAOC,MAAMC,IAAI,CAAC,IAAI,CAACL,QAAQ,CAACM,OAAO,IAAIC,GAAG,CAAC,CAAC,CAACC,aAAaC,MAAM,GAAM,CAAA;gBACxED;gBACAE,iBAAiBD;YACnB,CAAA;IACF;IAEA;;GAEC,GACDE,QAAc;QACZ,IAAI,CAACX,QAAQ,CAACY,KAAK;IACrB;IAEA;;GAEC,GACDC,WAAoB;QAClB,OAAO,IAAI,CAACb,QAAQ,CAACc,IAAI,GAAG;IAC9B;;aAhCQd,WAAW,IAAIe;;AAiCzB;AAEA,qBAAqB;AACrB,OAAO,MAAMC,sBAAsB,IAAIpB,sBAAqB;AAE5D;;CAEC,GACD,OAAO,SAASqB;IACd,OAAOD,oBAAoBb,SAAS;AACtC;AAEA;;CAEC,GACD,OAAO,SAASe;IACdF,oBAAoBL,KAAK;AAC3B;AAEA;;CAEC,GACD,OAAO,SAASQ,mBAAmBC,SAElC;IACC,MAAMC,YAAYJ;IAClB,IAAII,UAAUC,MAAM,KAAK,GAAG;QAC1B;IACF;IAEA,MAAM,EAAEC,iBAAiB,EAAE,GACzBC,QAAQ;IACV,MAAMC,SAASF,kBAAkBF;IACjC,KAAK,MAAMK,SAASD,OAAQ;QAC1BL,UAAUO,MAAM,CAACD;IACnB;AACF","ignoreList":[0]}
+95
View File
@@ -0,0 +1,95 @@
/**
* MCP tool for retrieving error state from Next.js dev server.
*
* This tool provides comprehensive error reporting including:
* - Next.js global errors (e.g., next.config validation errors)
* - Browser runtime errors with source-mapped stack traces
* - Build errors from webpack/turbopack compilation
*
* For browser errors, it leverages the HMR infrastructure for server-to-browser communication.
*
* Flow:
* MCP client → server generates request ID → HMR message to browser →
* browser queries error overlay state → HMR response back → server performs source mapping →
* combined with global errors → formatted output.
*/ import { HMR_MESSAGE_SENT_TO_BROWSER } from '../../dev/hot-reloader-types';
import { formatErrors } from './utils/format-errors';
import { createBrowserRequest, handleBrowserPageResponse, DEFAULT_BROWSER_REQUEST_TIMEOUT_MS } from './utils/browser-communication';
import { NextInstanceErrorState } from './next-instance-error-state';
import { mcpTelemetryTracker } from '../mcp-telemetry-tracker';
export function registerGetErrorsTool(server, sendHmrMessage, getActiveConnectionCount) {
server.registerTool('get_errors', {
description: 'Get the current error state from the Next.js dev server, including Next.js global errors (e.g., next.config validation), browser runtime errors, and build errors with source-mapped stack traces',
inputSchema: {}
}, async (_request)=>{
// Track telemetry
mcpTelemetryTracker.recordToolCall('mcp/get_errors');
try {
const connectionCount = getActiveConnectionCount();
if (connectionCount === 0) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: 'No browser sessions connected. Please open your application in a browser to retrieve error state.'
})
}
]
};
}
const responses = await createBrowserRequest(HMR_MESSAGE_SENT_TO_BROWSER.REQUEST_CURRENT_ERROR_STATE, sendHmrMessage, getActiveConnectionCount, DEFAULT_BROWSER_REQUEST_TIMEOUT_MS);
// The error state for each route
// key is the route path, value is the error state
const routesErrorState = new Map();
for (const response of responses){
if (response.data) {
routesErrorState.set(response.url, response.data);
}
}
const hasRouteErrors = Array.from(routesErrorState.values()).some((state)=>state.errors.length > 0 || !!state.buildError);
const hasInstanceErrors = NextInstanceErrorState.nextConfig.length > 0;
if (!hasRouteErrors && !hasInstanceErrors) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
configErrors: [],
sessionErrors: []
})
}
]
};
}
const output = await formatErrors(routesErrorState, NextInstanceErrorState);
return {
content: [
{
type: 'text',
text: JSON.stringify(output)
}
]
};
} catch (error) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: error instanceof Error ? error.message : String(error)
})
}
]
};
}
});
}
// Browser will first receive an HMR message from server to send back its error state.
// The actual state is sent back in a subsequent HMR message, which is handled by this function
// on the server.
export function handleErrorStateResponse(requestId, errorState, url) {
handleBrowserPageResponse(requestId, errorState, url || '');
}
//# sourceMappingURL=get-errors.js.map
File diff suppressed because one or more lines are too long
+57
View File
@@ -0,0 +1,57 @@
/**
* MCP tool for getting the path to the Next.js development log file.
*
* This tool returns the path to the {nextConfig.distDir}/logs/next-development.log file
* that contains browser console logs and other development information.
*/ import { stat } from 'fs/promises';
import { join } from 'path';
import { mcpTelemetryTracker } from '../mcp-telemetry-tracker';
export function registerGetLogsTool(server, distDir) {
server.registerTool('get_logs', {
description: 'Get the path to the Next.js development log file. Returns the file path so the agent can read the logs directly.'
}, async ()=>{
// Track telemetry
mcpTelemetryTracker.recordToolCall('mcp/get_logs');
try {
const logFilePath = join(distDir, 'logs', 'next-development.log');
// Check if the log file exists
try {
await stat(logFilePath);
} catch (error) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: `Log file not found at ${logFilePath}.`
})
}
]
};
}
return {
content: [
{
type: 'text',
text: JSON.stringify({
logFilePath
})
}
]
};
} catch (error) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: `Error getting log file path: ${error instanceof Error ? error.message : String(error)}`
})
}
]
};
}
});
}
//# sourceMappingURL=get-logs.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/server/mcp/tools/get-logs.ts"],"sourcesContent":["/**\n * MCP tool for getting the path to the Next.js development log file.\n *\n * This tool returns the path to the {nextConfig.distDir}/logs/next-development.log file\n * that contains browser console logs and other development information.\n */\nimport type { McpServer } from 'next/dist/compiled/@modelcontextprotocol/sdk/server/mcp'\nimport { stat } from 'fs/promises'\nimport { join } from 'path'\nimport { mcpTelemetryTracker } from '../mcp-telemetry-tracker'\n\nexport function registerGetLogsTool(server: McpServer, distDir: string) {\n server.registerTool(\n 'get_logs',\n {\n description:\n 'Get the path to the Next.js development log file. Returns the file path so the agent can read the logs directly.',\n },\n async () => {\n // Track telemetry\n mcpTelemetryTracker.recordToolCall('mcp/get_logs')\n\n try {\n const logFilePath = join(distDir, 'logs', 'next-development.log')\n\n // Check if the log file exists\n try {\n await stat(logFilePath)\n } catch (error) {\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({\n error: `Log file not found at ${logFilePath}.`,\n }),\n },\n ],\n }\n }\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({\n logFilePath,\n }),\n },\n ],\n }\n } catch (error) {\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({\n error: `Error getting log file path: ${error instanceof Error ? error.message : String(error)}`,\n }),\n },\n ],\n }\n }\n }\n )\n}\n"],"names":["stat","join","mcpTelemetryTracker","registerGetLogsTool","server","distDir","registerTool","description","recordToolCall","logFilePath","error","content","type","text","JSON","stringify","Error","message","String"],"mappings":"AAAA;;;;;CAKC,GAED,SAASA,IAAI,QAAQ,cAAa;AAClC,SAASC,IAAI,QAAQ,OAAM;AAC3B,SAASC,mBAAmB,QAAQ,2BAA0B;AAE9D,OAAO,SAASC,oBAAoBC,MAAiB,EAAEC,OAAe;IACpED,OAAOE,YAAY,CACjB,YACA;QACEC,aACE;IACJ,GACA;QACE,kBAAkB;QAClBL,oBAAoBM,cAAc,CAAC;QAEnC,IAAI;YACF,MAAMC,cAAcR,KAAKI,SAAS,QAAQ;YAE1C,+BAA+B;YAC/B,IAAI;gBACF,MAAML,KAAKS;YACb,EAAE,OAAOC,OAAO;gBACd,OAAO;oBACLC,SAAS;wBACP;4BACEC,MAAM;4BACNC,MAAMC,KAAKC,SAAS,CAAC;gCACnBL,OAAO,CAAC,sBAAsB,EAAED,YAAY,CAAC,CAAC;4BAChD;wBACF;qBACD;gBACH;YACF;YAEA,OAAO;gBACLE,SAAS;oBACP;wBACEC,MAAM;wBACNC,MAAMC,KAAKC,SAAS,CAAC;4BACnBN;wBACF;oBACF;iBACD;YACH;QACF,EAAE,OAAOC,OAAO;YACd,OAAO;gBACLC,SAAS;oBACP;wBACEC,MAAM;wBACNC,MAAMC,KAAKC,SAAS,CAAC;4BACnBL,OAAO,CAAC,6BAA6B,EAAEA,iBAAiBM,QAAQN,MAAMO,OAAO,GAAGC,OAAOR,QAAQ;wBACjG;oBACF;iBACD;YACH;QACF;IACF;AAEJ","ignoreList":[0]}
+166
View File
@@ -0,0 +1,166 @@
import { HMR_MESSAGE_SENT_TO_BROWSER } from '../../dev/hot-reloader-types';
import { createBrowserRequest, handleBrowserPageResponse, DEFAULT_BROWSER_REQUEST_TIMEOUT_MS } from './utils/browser-communication';
import { mcpTelemetryTracker } from '../mcp-telemetry-tracker';
export function registerGetPageMetadataTool(server, sendHmrMessage, getActiveConnectionCount) {
server.registerTool('get_page_metadata', {
description: 'Get runtime metadata about what contributes to the current page render from active browser sessions.',
inputSchema: {}
}, async (_request)=>{
// Track telemetry
mcpTelemetryTracker.recordToolCall('mcp/get_page_metadata');
try {
const connectionCount = getActiveConnectionCount();
if (connectionCount === 0) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: 'No browser sessions connected. Please open your application in a browser to retrieve page metadata.'
})
}
]
};
}
const responses = await createBrowserRequest(HMR_MESSAGE_SENT_TO_BROWSER.REQUEST_PAGE_METADATA, sendHmrMessage, getActiveConnectionCount, DEFAULT_BROWSER_REQUEST_TIMEOUT_MS);
if (responses.length === 0) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
sessions: []
})
}
]
};
}
const sessionMetadata = [];
for (const response of responses){
if (response.data) {
// TODO: Add other metadata for the current page render here. Currently, we only have segment trie data.
const pageMetadata = convertSegmentTrieToPageMetadata(response.data);
sessionMetadata.push({
url: response.url,
metadata: pageMetadata
});
}
}
if (sessionMetadata.length === 0) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
sessions: []
})
}
]
};
}
const output = formatPageMetadata(sessionMetadata);
return {
content: [
{
type: 'text',
text: JSON.stringify(output)
}
]
};
} catch (error) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: error instanceof Error ? error.message : String(error)
})
}
]
};
}
});
}
export function handlePageMetadataResponse(requestId, segmentTrieData, url) {
handleBrowserPageResponse(requestId, segmentTrieData, url || '');
}
function convertSegmentTrieToPageMetadata(data) {
const segments = [];
if (data.segmentTrie) {
// Traverse the trie and collect all segments
function traverseTrie(node) {
if (node.value) {
segments.push({
type: node.value.type,
pagePath: node.value.pagePath,
boundaryType: node.value.boundaryType
});
}
for (const childNode of Object.values(node.children)){
if (childNode) {
traverseTrie(childNode);
}
}
}
traverseTrie(data.segmentTrie);
}
return {
segments,
routerType: data.routerType
};
}
function formatPageMetadata(sessionMetadata) {
const sessions = [];
for (const { url, metadata } of sessionMetadata){
let displayUrl = url;
try {
const urlObj = new URL(url);
displayUrl = urlObj.pathname + urlObj.search + urlObj.hash;
} catch {
// If URL parsing fails, use the original URL
}
// Ensure consistent output to avoid flaky tests
const sortedSegments = [
...metadata.segments
].sort((a, b)=>{
const typeOrder = (segment)=>{
const type = segment.boundaryType || segment.type;
if (type === 'layout') return 0;
if (type.startsWith('boundary:')) return 1;
if (type === 'page') return 2;
return 3;
};
const aOrder = typeOrder(a);
const bOrder = typeOrder(b);
if (aOrder !== bOrder) return aOrder - bOrder;
return a.pagePath.localeCompare(b.pagePath);
});
const formattedSegments = [];
for (const segment of sortedSegments){
const path = segment.pagePath;
const isBuiltin = path.startsWith('__next_builtin__');
const type = segment.boundaryType || segment.type;
const isBoundary = type.startsWith('boundary:');
let displayPath = path.replace(/@boundary$/, '').replace(/^__next_builtin__/, '');
if (!isBuiltin && !displayPath.startsWith('app/')) {
displayPath = `app/${displayPath}`;
}
formattedSegments.push({
path: displayPath,
type,
isBoundary,
isBuiltin
});
}
sessions.push({
url: displayUrl,
routerType: metadata.routerType,
segments: formattedSegments
});
}
return {
sessions
};
}
//# sourceMappingURL=get-page-metadata.js.map
File diff suppressed because one or more lines are too long
+49
View File
@@ -0,0 +1,49 @@
import { mcpTelemetryTracker } from '../mcp-telemetry-tracker';
export function registerGetProjectMetadataTool(server, projectPath, getDevServerUrl) {
server.registerTool('get_project_metadata', {
description: 'Returns the the metadata of this Next.js project, including project path, dev server URL, etc.',
inputSchema: {}
}, async (_request)=>{
// Track telemetry
mcpTelemetryTracker.recordToolCall('mcp/get_project_metadata');
try {
if (!projectPath) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: 'Unable to determine the absolute path of the Next.js project.'
})
}
]
};
}
const devServerUrl = getDevServerUrl();
return {
content: [
{
type: 'text',
text: JSON.stringify({
projectPath,
devServerUrl
})
}
]
};
} catch (error) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: error instanceof Error ? error.message : String(error)
})
}
]
};
}
});
}
//# sourceMappingURL=get-project-metadata.js.map
@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/server/mcp/tools/get-project-metadata.ts"],"sourcesContent":["import type { McpServer } from 'next/dist/compiled/@modelcontextprotocol/sdk/server/mcp'\nimport { mcpTelemetryTracker } from '../mcp-telemetry-tracker'\n\nexport function registerGetProjectMetadataTool(\n server: McpServer,\n projectPath: string,\n getDevServerUrl: () => string | undefined\n) {\n server.registerTool(\n 'get_project_metadata',\n {\n description:\n 'Returns the the metadata of this Next.js project, including project path, dev server URL, etc.',\n inputSchema: {},\n },\n async (_request) => {\n // Track telemetry\n mcpTelemetryTracker.recordToolCall('mcp/get_project_metadata')\n\n try {\n if (!projectPath) {\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({\n error:\n 'Unable to determine the absolute path of the Next.js project.',\n }),\n },\n ],\n }\n }\n\n const devServerUrl = getDevServerUrl()\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({\n projectPath,\n devServerUrl,\n }),\n },\n ],\n }\n } catch (error) {\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({\n error: error instanceof Error ? error.message : String(error),\n }),\n },\n ],\n }\n }\n }\n )\n}\n"],"names":["mcpTelemetryTracker","registerGetProjectMetadataTool","server","projectPath","getDevServerUrl","registerTool","description","inputSchema","_request","recordToolCall","content","type","text","JSON","stringify","error","devServerUrl","Error","message","String"],"mappings":"AACA,SAASA,mBAAmB,QAAQ,2BAA0B;AAE9D,OAAO,SAASC,+BACdC,MAAiB,EACjBC,WAAmB,EACnBC,eAAyC;IAEzCF,OAAOG,YAAY,CACjB,wBACA;QACEC,aACE;QACFC,aAAa,CAAC;IAChB,GACA,OAAOC;QACL,kBAAkB;QAClBR,oBAAoBS,cAAc,CAAC;QAEnC,IAAI;YACF,IAAI,CAACN,aAAa;gBAChB,OAAO;oBACLO,SAAS;wBACP;4BACEC,MAAM;4BACNC,MAAMC,KAAKC,SAAS,CAAC;gCACnBC,OACE;4BACJ;wBACF;qBACD;gBACH;YACF;YAEA,MAAMC,eAAeZ;YAErB,OAAO;gBACLM,SAAS;oBACP;wBACEC,MAAM;wBACNC,MAAMC,KAAKC,SAAS,CAAC;4BACnBX;4BACAa;wBACF;oBACF;iBACD;YACH;QACF,EAAE,OAAOD,OAAO;YACd,OAAO;gBACLL,SAAS;oBACP;wBACEC,MAAM;wBACNC,MAAMC,KAAKC,SAAS,CAAC;4BACnBC,OAAOA,iBAAiBE,QAAQF,MAAMG,OAAO,GAAGC,OAAOJ;wBACzD;oBACF;iBACD;YACH;QACF;IACF;AAEJ","ignoreList":[0]}
+121
View File
@@ -0,0 +1,121 @@
/**
* MCP tool for getting all routes that become entry points in a Next.js application.
*
* This tool discovers routes by scanning the filesystem directly. It finds all route
* files in the app/ and pages/ directories and converts them to route paths.
*
* Returns routes grouped by router type:
* - appRouter: App Router pages and route handlers
* - pagesRouter: Pages Router pages and API routes
*
* Dynamic route segments appear as [id], [slug], or [...slug] patterns. This tool
* does NOT expand getStaticParams - it only shows the route patterns as defined in
* the filesystem.
*/ import { mcpTelemetryTracker } from '../mcp-telemetry-tracker';
import { discoverRoutes } from '../../../build/route-discovery';
import z from 'next/dist/compiled/zod';
export function registerGetRoutesTool(server, options) {
server.registerTool('get_routes', {
description: 'Get all routes that will become entry points in the Next.js application by scanning the filesystem. Returns routes grouped by router type (appRouter, pagesRouter). Dynamic segments appear as [param] or [...slug] patterns. API routes are included in their respective routers (e.g., /api/* routes from pages/ are in pagesRouter). Optional parameter: routerType ("app" | "pages") - filter by specific router type, omit to get all routes.',
inputSchema: {
routerType: z.union([
z.literal('app'),
z.literal('pages')
]).optional()
}
}, async (request)=>{
// Track telemetry
mcpTelemetryTracker.recordToolCall('mcp/get_routes');
try {
const routerType = request.routerType === 'app' || request.routerType === 'pages' ? request.routerType : undefined;
const { projectPath, nextConfig, pagesDir, appDir } = options;
// Check if we have any directories to scan
if (!pagesDir && !appDir) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: 'No pages or app directory found in the project.'
})
}
]
};
}
const isSrcDir = pagesDir && pagesDir.includes('/src/') || appDir && appDir.includes('/src/');
const commonOpts = {
pageExtensions: nextConfig.pageExtensions,
isDev: true,
baseDir: projectPath,
isSrcDir: !!isSrcDir
};
// Discover app and pages routes independently so a failure in one
// router doesn't prevent the other from returning results.
let appRoutes = [];
let pageRoutes = [];
const wantApp = routerType !== 'pages' && appDir;
const wantPages = routerType !== 'app' && pagesDir;
const [appResult, pagesResult] = await Promise.all([
wantApp ? discoverRoutes({
...commonOpts,
appDir
}).catch(()=>null) : null,
wantPages ? discoverRoutes({
...commonOpts,
pagesDir
}).catch(()=>null) : null
]);
if (appResult) {
appRoutes = [
...appResult.appRoutes,
...appResult.appRouteHandlers
].map((r)=>r.route).sort();
}
if (pagesResult) {
pageRoutes = [
...pagesResult.pageRoutes,
...pagesResult.pageApiRoutes
].map((r)=>r.route).sort();
}
if (appRoutes.length === 0 && pageRoutes.length === 0) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
appRouter: [],
pagesRouter: []
})
}
]
};
}
// Format the output with grouped routes
const output = {
appRouter: appRoutes.length > 0 ? appRoutes : undefined,
pagesRouter: pageRoutes.length > 0 ? pageRoutes : undefined
};
return {
content: [
{
type: 'text',
text: JSON.stringify(output, null, 2)
}
]
};
} catch (error) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: error instanceof Error ? error.message : String(error)
})
}
]
};
}
});
}
//# sourceMappingURL=get-routes.js.map
File diff suppressed because one or more lines are too long
+111
View File
@@ -0,0 +1,111 @@
import { z } from 'next/dist/compiled/zod';
import { promises as fs } from 'fs';
import { join } from 'path';
import { mcpTelemetryTracker } from '../mcp-telemetry-tracker';
const INLINE_ACTION_PREFIX = '$$RSC_SERVER_ACTION_';
export function registerGetActionByIdTool(server, distDir) {
server.registerTool('get_server_action_by_id', {
description: 'Locates a Server Action by its ID in the server-reference-manifest.json. Returns the filename and export name for the action.',
inputSchema: {
actionId: z.string()
}
}, async (request)=>{
// Track telemetry
mcpTelemetryTracker.recordToolCall('mcp/get_server_action_by_id');
try {
const { actionId } = request;
if (!actionId) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: 'actionId parameter is required'
})
}
]
};
}
const manifestPath = join(distDir, 'server', 'server-reference-manifest.json');
let manifestContent;
try {
manifestContent = await fs.readFile(manifestPath, 'utf-8');
} catch (error) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: `Could not read server-reference-manifest.json at ${manifestPath}.`
})
}
]
};
}
const manifest = JSON.parse(manifestContent);
// Search in node entries
if (manifest.node && manifest.node[actionId]) {
const entry = manifest.node[actionId];
const isInlineAction = entry.exportedName.startsWith(INLINE_ACTION_PREFIX);
return {
content: [
{
type: 'text',
text: JSON.stringify({
actionId,
runtime: 'node',
filename: entry.filename,
functionName: isInlineAction ? 'inline server action' : entry.exportedName,
layer: entry.layer,
workers: entry.workers
}, null, 2)
}
]
};
}
// Search in edge entries
if (manifest.edge && manifest.edge[actionId]) {
const entry = manifest.edge[actionId];
const isInlineAction = entry.exportedName.startsWith(INLINE_ACTION_PREFIX);
return {
content: [
{
type: 'text',
text: JSON.stringify({
actionId,
runtime: 'edge',
filename: entry.filename,
functionName: isInlineAction ? 'inline server action' : entry.exportedName,
layer: entry.layer,
workers: entry.workers
}, null, 2)
}
]
};
}
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: `Action ID "${actionId}" not found in server-reference-manifest.json`
})
}
]
};
} catch (error) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: error instanceof Error ? error.message : String(error)
})
}
]
};
}
});
}
//# sourceMappingURL=get-server-action-by-id.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,21 @@
/**
* Global error state for Next.js instance-level errors that are not associated
* with a specific browser session or route. This state is exposed through the MCP server's `get_errors`
* tool as well. This covers the errors that are global to the Next.js instance, such as errors in next.config.js.
*
*
* ## Usage
*
* This state is directly manipulated by various parts of the Next.js dev server:
*
* // Reset the error state
* NextInstanceErrorState.[errorType] = []
*
* // Capture an error for a specific error type
* NextInstanceErrorState.[errorType].push(err)
*
*/ export const NextInstanceErrorState = {
nextConfig: []
};
//# sourceMappingURL=next-instance-error-state.js.map
@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/server/mcp/tools/next-instance-error-state.ts"],"sourcesContent":["/**\n * Global error state for Next.js instance-level errors that are not associated\n * with a specific browser session or route. This state is exposed through the MCP server's `get_errors`\n * tool as well. This covers the errors that are global to the Next.js instance, such as errors in next.config.js.\n *\n *\n * ## Usage\n *\n * This state is directly manipulated by various parts of the Next.js dev server:\n *\n * // Reset the error state\n * NextInstanceErrorState.[errorType] = []\n *\n * // Capture an error for a specific error type\n * NextInstanceErrorState.[errorType].push(err)\n *\n */\nexport const NextInstanceErrorState: {\n nextConfig: unknown[]\n} = {\n nextConfig: [],\n}\n"],"names":["NextInstanceErrorState","nextConfig"],"mappings":"AAAA;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,MAAMA,yBAET;IACFC,YAAY,EAAE;AAChB,EAAC","ignoreList":[0]}
@@ -0,0 +1,64 @@
/**
* Shared utilities for MCP tools that communicate with the browser.
* This module provides a common infrastructure for request-response
* communication between MCP endpoints and browser sessions via HMR.
*/ import { nanoid } from 'next/dist/compiled/nanoid';
export const DEFAULT_BROWSER_REQUEST_TIMEOUT_MS = 5000;
const pendingRequests = new Map();
export function createBrowserRequest(messageType, sendHmrMessage, getActiveConnectionCount, timeoutMs) {
const connectionCount = getActiveConnectionCount();
if (connectionCount === 0) {
return Promise.resolve([]);
}
const requestId = `mcp-${messageType}-${nanoid()}`;
const responsePromise = new Promise((resolve, reject)=>{
const timeout = setTimeout(()=>{
const pending = pendingRequests.get(requestId);
if (pending && pending.responses.length > 0) {
resolve(pending.responses);
} else {
reject(Object.defineProperty(new Error(`Timeout waiting for response from frontend. The browser may not be responding to HMR messages.`), "__NEXT_ERROR_CODE", {
value: "E825",
enumerable: false,
configurable: true
}));
}
pendingRequests.delete(requestId);
}, timeoutMs);
pendingRequests.set(requestId, {
responses: [],
expectedCount: connectionCount,
resolve: resolve,
reject,
timeout
});
});
sendHmrMessage({
type: messageType,
requestId
});
return responsePromise;
}
export function handleBrowserPageResponse(requestId, data, url) {
if (!url) {
throw Object.defineProperty(new Error('URL is required in MCP browser response. This is a bug in Next.js.'), "__NEXT_ERROR_CODE", {
value: "E824",
enumerable: false,
configurable: true
});
}
const pending = pendingRequests.get(requestId);
if (pending) {
pending.responses.push({
url,
data
});
if (pending.responses.length >= pending.expectedCount) {
clearTimeout(pending.timeout);
pending.resolve(pending.responses);
pendingRequests.delete(requestId);
}
}
}
//# sourceMappingURL=browser-communication.js.map
@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../../src/server/mcp/tools/utils/browser-communication.ts"],"sourcesContent":["/**\n * Shared utilities for MCP tools that communicate with the browser.\n * This module provides a common infrastructure for request-response\n * communication between MCP endpoints and browser sessions via HMR.\n */\n\nimport { nanoid } from 'next/dist/compiled/nanoid'\nimport type {\n HMR_MESSAGE_SENT_TO_BROWSER,\n HmrMessageSentToBrowser,\n} from '../../../dev/hot-reloader-types'\n\nexport const DEFAULT_BROWSER_REQUEST_TIMEOUT_MS = 5000\n\nexport type BrowserResponse<T> = {\n url: string\n data: T\n}\n\ntype PendingRequest<T> = {\n responses: BrowserResponse<T>[]\n expectedCount: number\n resolve: (value: BrowserResponse<T>[]) => void\n reject: (reason?: unknown) => void\n timeout: NodeJS.Timeout\n}\n\nconst pendingRequests = new Map<string, PendingRequest<unknown>>()\n\nexport function createBrowserRequest<T>(\n messageType: HMR_MESSAGE_SENT_TO_BROWSER,\n sendHmrMessage: (message: HmrMessageSentToBrowser) => void,\n getActiveConnectionCount: () => number,\n timeoutMs: number\n): Promise<BrowserResponse<T>[]> {\n const connectionCount = getActiveConnectionCount()\n if (connectionCount === 0) {\n return Promise.resolve([])\n }\n\n const requestId = `mcp-${messageType}-${nanoid()}`\n\n const responsePromise = new Promise<BrowserResponse<T>[]>(\n (resolve, reject) => {\n const timeout = setTimeout(() => {\n const pending = pendingRequests.get(requestId)\n if (pending && pending.responses.length > 0) {\n resolve(pending.responses as BrowserResponse<T>[])\n } else {\n reject(\n new Error(\n `Timeout waiting for response from frontend. The browser may not be responding to HMR messages.`\n )\n )\n }\n pendingRequests.delete(requestId)\n }, timeoutMs)\n\n pendingRequests.set(requestId, {\n responses: [],\n expectedCount: connectionCount,\n resolve: resolve as (value: BrowserResponse<unknown>[]) => void,\n reject,\n timeout,\n })\n }\n )\n\n sendHmrMessage({\n type: messageType,\n requestId,\n } as HmrMessageSentToBrowser)\n\n return responsePromise\n}\n\nexport function handleBrowserPageResponse<T>(\n requestId: string,\n data: T,\n url: string\n): void {\n if (!url) {\n throw new Error(\n 'URL is required in MCP browser response. This is a bug in Next.js.'\n )\n }\n\n const pending = pendingRequests.get(requestId)\n if (pending) {\n pending.responses.push({ url, data })\n if (pending.responses.length >= pending.expectedCount) {\n clearTimeout(pending.timeout)\n pending.resolve(pending.responses)\n pendingRequests.delete(requestId)\n }\n }\n}\n"],"names":["nanoid","DEFAULT_BROWSER_REQUEST_TIMEOUT_MS","pendingRequests","Map","createBrowserRequest","messageType","sendHmrMessage","getActiveConnectionCount","timeoutMs","connectionCount","Promise","resolve","requestId","responsePromise","reject","timeout","setTimeout","pending","get","responses","length","Error","delete","set","expectedCount","type","handleBrowserPageResponse","data","url","push","clearTimeout"],"mappings":"AAAA;;;;CAIC,GAED,SAASA,MAAM,QAAQ,4BAA2B;AAMlD,OAAO,MAAMC,qCAAqC,KAAI;AAetD,MAAMC,kBAAkB,IAAIC;AAE5B,OAAO,SAASC,qBACdC,WAAwC,EACxCC,cAA0D,EAC1DC,wBAAsC,EACtCC,SAAiB;IAEjB,MAAMC,kBAAkBF;IACxB,IAAIE,oBAAoB,GAAG;QACzB,OAAOC,QAAQC,OAAO,CAAC,EAAE;IAC3B;IAEA,MAAMC,YAAY,CAAC,IAAI,EAAEP,YAAY,CAAC,EAAEL,UAAU;IAElD,MAAMa,kBAAkB,IAAIH,QAC1B,CAACC,SAASG;QACR,MAAMC,UAAUC,WAAW;YACzB,MAAMC,UAAUf,gBAAgBgB,GAAG,CAACN;YACpC,IAAIK,WAAWA,QAAQE,SAAS,CAACC,MAAM,GAAG,GAAG;gBAC3CT,QAAQM,QAAQE,SAAS;YAC3B,OAAO;gBACLL,OACE,qBAEC,CAFD,IAAIO,MACF,CAAC,8FAA8F,CAAC,GADlG,qBAAA;2BAAA;gCAAA;kCAAA;gBAEA;YAEJ;YACAnB,gBAAgBoB,MAAM,CAACV;QACzB,GAAGJ;QAEHN,gBAAgBqB,GAAG,CAACX,WAAW;YAC7BO,WAAW,EAAE;YACbK,eAAef;YACfE,SAASA;YACTG;YACAC;QACF;IACF;IAGFT,eAAe;QACbmB,MAAMpB;QACNO;IACF;IAEA,OAAOC;AACT;AAEA,OAAO,SAASa,0BACdd,SAAiB,EACjBe,IAAO,EACPC,GAAW;IAEX,IAAI,CAACA,KAAK;QACR,MAAM,qBAEL,CAFK,IAAIP,MACR,uEADI,qBAAA;mBAAA;wBAAA;0BAAA;QAEN;IACF;IAEA,MAAMJ,UAAUf,gBAAgBgB,GAAG,CAACN;IACpC,IAAIK,SAAS;QACXA,QAAQE,SAAS,CAACU,IAAI,CAAC;YAAED;YAAKD;QAAK;QACnC,IAAIV,QAAQE,SAAS,CAACC,MAAM,IAAIH,QAAQO,aAAa,EAAE;YACrDM,aAAab,QAAQF,OAAO;YAC5BE,QAAQN,OAAO,CAACM,QAAQE,SAAS;YACjCjB,gBAAgBoB,MAAM,CAACV;QACzB;IACF;AACF","ignoreList":[0]}
+115
View File
@@ -0,0 +1,115 @@
import { getErrorSource } from '../../../../shared/lib/error-source';
// Dependency injection for stack frame resolver
let stackFrameResolver;
export function setStackFrameResolver(fn) {
stackFrameResolver = fn;
}
async function resolveStackFrames(request) {
if (!stackFrameResolver) {
throw Object.defineProperty(new Error('Stack frame resolver not initialized. This is a bug in Next.js.'), "__NEXT_ERROR_CODE", {
value: "E822",
enumerable: false,
configurable: true
});
}
return stackFrameResolver(request);
}
const formatStackFrameToObject = (frame)=>{
return {
file: frame.file || '<unknown>',
methodName: frame.methodName || '<anonymous>',
line: frame.line1,
column: frame.column1
};
};
const resolveErrorFrames = async (frames, context)=>{
try {
const resolvedFrames = await resolveStackFrames({
frames: frames.map((frame)=>({
file: frame.file || null,
methodName: frame.methodName || '<anonymous>',
arguments: [],
line1: frame.line1 || null,
column1: frame.column1 || null
})),
isServer: context.isServer,
isEdgeServer: context.isEdgeServer,
isAppDirectory: context.isAppDirectory
});
return resolvedFrames.filter((resolvedFrame)=>{
var _resolvedFrame_value_originalStackFrame;
return !(resolvedFrame.status === 'fulfilled' && ((_resolvedFrame_value_originalStackFrame = resolvedFrame.value.originalStackFrame) == null ? void 0 : _resolvedFrame_value_originalStackFrame.ignored));
}).map((resolvedFrame, j)=>resolvedFrame.status === 'fulfilled' && resolvedFrame.value.originalStackFrame ? formatStackFrameToObject(resolvedFrame.value.originalStackFrame) : formatStackFrameToObject(frames[j]));
} catch {
return frames.map(formatStackFrameToObject);
}
};
async function formatRuntimeErrorsToObjects(errors, isAppDirectory) {
const formattedErrors = [];
for (const error of errors){
var _error_error, _error_error1, _error_frames;
const errorName = ((_error_error = error.error) == null ? void 0 : _error_error.name) || 'Error';
const errorMsg = ((_error_error1 = error.error) == null ? void 0 : _error_error1.message) || 'Unknown error';
let stack = [];
if ((_error_frames = error.frames) == null ? void 0 : _error_frames.length) {
const errorSource = getErrorSource(error.error);
stack = await resolveErrorFrames(error.frames, {
isServer: errorSource === 'server',
isEdgeServer: errorSource === 'edge-server',
isAppDirectory
});
}
formattedErrors.push({
type: error.type,
errorName,
message: errorMsg,
stack
});
}
return formattedErrors;
}
export async function formatErrors(errorsByUrl, nextInstanceErrors = {
nextConfig: []
}) {
const output = {
configErrors: [],
sessionErrors: []
};
// Format Next.js instance errors first (e.g., next.config.js errors)
for (const error of nextInstanceErrors.nextConfig){
if (error instanceof Error) {
output.configErrors.push({
name: error.name,
message: error.message,
stack: error.stack || null
});
} else {
output.configErrors.push({
name: 'Error',
message: String(error),
stack: null
});
}
}
// Format browser session errors
for (const [url, overlayState] of errorsByUrl){
const totalErrorCount = overlayState.errors.length + (overlayState.buildError ? 1 : 0);
if (totalErrorCount === 0) continue;
let displayUrl = url;
try {
const urlObj = new URL(url);
displayUrl = urlObj.pathname + urlObj.search + urlObj.hash;
} catch {
// If URL parsing fails, use the original URL
}
const runtimeErrors = await formatRuntimeErrorsToObjects(overlayState.errors, overlayState.routerType === 'app');
output.sessionErrors.push({
url: displayUrl,
buildError: overlayState.buildError || null,
runtimeErrors
});
}
return output;
}
//# sourceMappingURL=format-errors.js.map
File diff suppressed because one or more lines are too long