Skip to content

Commit bb88ea5

Browse files
authored
[mcp] change the mcp endpoint response to JSON (vercel#88911)
1 parent d1cf4e6 commit bb88ea5

File tree

11 files changed

+509
-603
lines changed

11 files changed

+509
-603
lines changed

packages/next/src/server/mcp/tools/get-errors.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ export function registerGetErrorsTool(
5151
content: [
5252
{
5353
type: 'text',
54-
text: 'No browser sessions connected. Please open your application in a browser to retrieve error state.',
54+
text: JSON.stringify({
55+
error:
56+
'No browser sessions connected. Please open your application in a browser to retrieve error state.',
57+
}),
5558
},
5659
],
5760
}
@@ -83,10 +86,10 @@ export function registerGetErrorsTool(
8386
content: [
8487
{
8588
type: 'text',
86-
text:
87-
responses.length === 0
88-
? 'No browser sessions responded.'
89-
: `No errors detected in ${responses.length} browser session(s).`,
89+
text: JSON.stringify({
90+
configErrors: [],
91+
sessionErrors: [],
92+
}),
9093
},
9194
],
9295
}
@@ -101,7 +104,7 @@ export function registerGetErrorsTool(
101104
content: [
102105
{
103106
type: 'text',
104-
text: output,
107+
text: JSON.stringify(output),
105108
},
106109
],
107110
}
@@ -110,7 +113,9 @@ export function registerGetErrorsTool(
110113
content: [
111114
{
112115
type: 'text',
113-
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
116+
text: JSON.stringify({
117+
error: error instanceof Error ? error.message : String(error),
118+
}),
114119
},
115120
],
116121
}

packages/next/src/server/mcp/tools/get-logs.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ export function registerGetLogsTool(server: McpServer, distDir: string) {
3131
content: [
3232
{
3333
type: 'text',
34-
text: `Log file not found at ${logFilePath}.`,
34+
text: JSON.stringify({
35+
error: `Log file not found at ${logFilePath}.`,
36+
}),
3537
},
3638
],
3739
}
@@ -41,7 +43,9 @@ export function registerGetLogsTool(server: McpServer, distDir: string) {
4143
content: [
4244
{
4345
type: 'text',
44-
text: `Next.js log file path: ${logFilePath}`,
46+
text: JSON.stringify({
47+
logFilePath,
48+
}),
4549
},
4650
],
4751
}
@@ -50,7 +54,9 @@ export function registerGetLogsTool(server: McpServer, distDir: string) {
5054
content: [
5155
{
5256
type: 'text',
53-
text: `Error getting log file path: ${error instanceof Error ? error.message : String(error)}`,
57+
text: JSON.stringify({
58+
error: `Error getting log file path: ${error instanceof Error ? error.message : String(error)}`,
59+
}),
5460
},
5561
],
5662
}

packages/next/src/server/mcp/tools/get-page-metadata.ts

Lines changed: 73 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ export function registerGetPageMetadataTool(
3939
content: [
4040
{
4141
type: 'text',
42-
text: 'No browser sessions connected. Please open your application in a browser to retrieve page metadata.',
42+
text: JSON.stringify({
43+
error:
44+
'No browser sessions connected. Please open your application in a browser to retrieve page metadata.',
45+
}),
4346
},
4447
],
4548
}
@@ -57,7 +60,9 @@ export function registerGetPageMetadataTool(
5760
content: [
5861
{
5962
type: 'text',
60-
text: 'No browser sessions responded.',
63+
text: JSON.stringify({
64+
sessions: [],
65+
}),
6166
},
6267
],
6368
}
@@ -78,7 +83,9 @@ export function registerGetPageMetadataTool(
7883
content: [
7984
{
8085
type: 'text',
81-
text: `No page metadata available from ${responses.length} browser session(s).`,
86+
text: JSON.stringify({
87+
sessions: [],
88+
}),
8289
},
8390
],
8491
}
@@ -90,7 +97,7 @@ export function registerGetPageMetadataTool(
9097
content: [
9198
{
9299
type: 'text',
93-
text: output,
100+
text: JSON.stringify(output),
94101
},
95102
],
96103
}
@@ -99,7 +106,9 @@ export function registerGetPageMetadataTool(
99106
content: [
100107
{
101108
type: 'text',
102-
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
109+
text: JSON.stringify({
110+
error: error instanceof Error ? error.message : String(error),
111+
}),
103112
},
104113
],
105114
}
@@ -150,10 +159,27 @@ function convertSegmentTrieToPageMetadata(data: SegmentTrieData): PageMetadata {
150159
}
151160
}
152161

162+
interface FormattedSegment {
163+
path: string
164+
type: string
165+
isBoundary: boolean
166+
isBuiltin: boolean
167+
}
168+
169+
interface FormattedSession {
170+
url: string
171+
routerType: string
172+
segments: FormattedSegment[]
173+
}
174+
175+
interface FormattedPageMetadataOutput {
176+
sessions: FormattedSession[]
177+
}
178+
153179
function formatPageMetadata(
154180
sessionMetadata: Array<{ url: string; metadata: PageMetadata }>
155-
): string {
156-
let output = `# Page metadata from ${sessionMetadata.length} browser session(s)\n\n`
181+
): FormattedPageMetadataOutput {
182+
const sessions: FormattedSession[] = []
157183

158184
for (const { url, metadata } of sessionMetadata) {
159185
let displayUrl = url
@@ -164,56 +190,50 @@ function formatPageMetadata(
164190
// If URL parsing fails, use the original URL
165191
}
166192

167-
output += `## Session: ${displayUrl}\n\n`
168-
output += `**Router type:** ${metadata.routerType}\n\n`
169-
170-
if (metadata.segments.length === 0) {
171-
output += '*No segments found*\n\n'
172-
} else {
173-
output += '### Files powering this page:\n\n'
174-
175-
// Ensure consistent output to avoid flaky tests
176-
const sortedSegments = [...metadata.segments].sort((a, b) => {
177-
const typeOrder = (segment: PageSegment): number => {
178-
const type = segment.boundaryType || segment.type
179-
if (type === 'layout') return 0
180-
if (type.startsWith('boundary:')) return 1
181-
if (type === 'page') return 2
182-
return 3
183-
}
184-
const aOrder = typeOrder(a)
185-
const bOrder = typeOrder(b)
186-
if (aOrder !== bOrder) return aOrder - bOrder
187-
return a.pagePath.localeCompare(b.pagePath)
188-
})
189-
190-
for (const segment of sortedSegments) {
191-
const path = segment.pagePath
192-
const isBuiltin = path.startsWith('__next_builtin__')
193+
// Ensure consistent output to avoid flaky tests
194+
const sortedSegments = [...metadata.segments].sort((a, b) => {
195+
const typeOrder = (segment: PageSegment): number => {
193196
const type = segment.boundaryType || segment.type
194-
const isBoundary = type.startsWith('boundary:')
195-
196-
let displayPath = path
197-
.replace(/@boundary$/, '')
198-
.replace(/^__next_builtin__/, '')
199-
200-
if (!isBuiltin && !displayPath.startsWith('app/')) {
201-
displayPath = `app/${displayPath}`
202-
}
203-
204-
const descriptors: string[] = []
205-
if (isBoundary) descriptors.push('boundary')
206-
if (isBuiltin) descriptors.push('builtin')
207-
208-
const descriptor =
209-
descriptors.length > 0 ? ` (${descriptors.join(', ')})` : ''
210-
output += `- ${displayPath}${descriptor}\n`
197+
if (type === 'layout') return 0
198+
if (type.startsWith('boundary:')) return 1
199+
if (type === 'page') return 2
200+
return 3
201+
}
202+
const aOrder = typeOrder(a)
203+
const bOrder = typeOrder(b)
204+
if (aOrder !== bOrder) return aOrder - bOrder
205+
return a.pagePath.localeCompare(b.pagePath)
206+
})
207+
208+
const formattedSegments: FormattedSegment[] = []
209+
for (const segment of sortedSegments) {
210+
const path = segment.pagePath
211+
const isBuiltin = path.startsWith('__next_builtin__')
212+
const type = segment.boundaryType || segment.type
213+
const isBoundary = type.startsWith('boundary:')
214+
215+
let displayPath = path
216+
.replace(/@boundary$/, '')
217+
.replace(/^__next_builtin__/, '')
218+
219+
if (!isBuiltin && !displayPath.startsWith('app/')) {
220+
displayPath = `app/${displayPath}`
211221
}
212-
output += '\n'
222+
223+
formattedSegments.push({
224+
path: displayPath,
225+
type,
226+
isBoundary,
227+
isBuiltin,
228+
})
213229
}
214230

215-
output += '---\n\n'
231+
sessions.push({
232+
url: displayUrl,
233+
routerType: metadata.routerType,
234+
segments: formattedSegments,
235+
})
216236
}
217237

218-
return output.trim()
238+
return { sessions }
219239
}

packages/next/src/server/mcp/tools/get-project-metadata.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ export function registerGetProjectMetadataTool(
2323
content: [
2424
{
2525
type: 'text',
26-
text: 'Unable to determine the absolute path of the Next.js project.',
26+
text: JSON.stringify({
27+
error:
28+
'Unable to determine the absolute path of the Next.js project.',
29+
}),
2730
},
2831
],
2932
}
@@ -47,7 +50,9 @@ export function registerGetProjectMetadataTool(
4750
content: [
4851
{
4952
type: 'text',
50-
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
53+
text: JSON.stringify({
54+
error: error instanceof Error ? error.message : String(error),
55+
}),
5156
},
5257
],
5358
}

packages/next/src/server/mcp/tools/get-routes.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ export function registerGetRoutesTool(
6969
content: [
7070
{
7171
type: 'text',
72-
text: 'No pages or app directory found in the project.',
72+
text: JSON.stringify({
73+
error: 'No pages or app directory found in the project.',
74+
}),
7375
},
7476
],
7577
}
@@ -180,7 +182,10 @@ export function registerGetRoutesTool(
180182
content: [
181183
{
182184
type: 'text',
183-
text: 'No routes found in the project.',
185+
text: JSON.stringify({
186+
appRouter: [],
187+
pagesRouter: [],
188+
}),
184189
},
185190
],
186191
}
@@ -215,7 +220,9 @@ export function registerGetRoutesTool(
215220
content: [
216221
{
217222
type: 'text',
218-
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
223+
text: JSON.stringify({
224+
error: error instanceof Error ? error.message : String(error),
225+
}),
219226
},
220227
],
221228
}

packages/next/src/server/mcp/tools/get-server-action-by-id.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ export function registerGetActionByIdTool(server: McpServer, distDir: string) {
4141
content: [
4242
{
4343
type: 'text',
44-
text: 'Error: actionId parameter is required',
44+
text: JSON.stringify({
45+
error: 'actionId parameter is required',
46+
}),
4547
},
4648
],
4749
}
@@ -61,7 +63,9 @@ export function registerGetActionByIdTool(server: McpServer, distDir: string) {
6163
content: [
6264
{
6365
type: 'text',
64-
text: `Error: Could not read server-reference-manifest.json at ${manifestPath}.`,
66+
text: JSON.stringify({
67+
error: `Could not read server-reference-manifest.json at ${manifestPath}.`,
68+
}),
6569
},
6670
],
6771
}
@@ -129,7 +133,9 @@ export function registerGetActionByIdTool(server: McpServer, distDir: string) {
129133
content: [
130134
{
131135
type: 'text',
132-
text: `Error: Action ID "${actionId}" not found in server-reference-manifest.json`,
136+
text: JSON.stringify({
137+
error: `Action ID "${actionId}" not found in server-reference-manifest.json`,
138+
}),
133139
},
134140
],
135141
}
@@ -138,7 +144,9 @@ export function registerGetActionByIdTool(server: McpServer, distDir: string) {
138144
content: [
139145
{
140146
type: 'text',
141-
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
147+
text: JSON.stringify({
148+
error: error instanceof Error ? error.message : String(error),
149+
}),
142150
},
143151
],
144152
}

0 commit comments

Comments
 (0)