Skip to content

Commit 60bc7f5

Browse files
authored
improve next-runtime tool description (#19)
1 parent d039816 commit 60bc7f5

File tree

2 files changed

+127
-20
lines changed

2 files changed

+127
-20
lines changed

src/lib/nextjs-runtime-manager.ts

Lines changed: 81 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,27 +33,49 @@ async function findNextJsServers(): Promise<NextJsServerInfo[]> {
3333
const { stdout } = await execAsync("ps aux")
3434
const lines = stdout.split("\n")
3535
const servers: NextJsServerInfo[] = []
36+
const seenPorts = new Set<number>()
3637

3738
for (const line of lines) {
38-
if (line.includes("next dev") || line.includes("next-server")) {
39+
// Enhanced detection patterns for Next.js servers
40+
const isNextJsProcess =
41+
line.includes("next dev") ||
42+
line.includes("next-server") ||
43+
line.includes("next/dist/bin/next") ||
44+
(line.includes("node") && line.includes("next") && line.includes("dev"))
45+
46+
if (isNextJsProcess) {
3947
const parts = line.trim().split(/\s+/)
4048
const pid = parseInt(parts[1], 10)
4149
const command = parts.slice(10).join(" ")
4250

43-
const portMatch = command.match(/--port[=\s]+(\d+)/) || command.match(/:(\d+)/)
44-
let port = 3000
51+
// Try multiple methods to detect port
52+
const portMatch =
53+
command.match(/--port[=\s]+(\d+)/) ||
54+
command.match(/-p[=\s]+(\d+)/) ||
55+
command.match(/:(\d+)/)
56+
57+
let port = 3000 // Default Next.js port
4558

4659
if (portMatch) {
4760
port = parseInt(portMatch[1], 10)
4861
} else {
49-
const processInfo = await execAsync(`lsof -Pan -p ${pid} -i 2>/dev/null || true`)
50-
const portFromLsof = processInfo.stdout.match(/:(\d+).*LISTEN/)
51-
if (portFromLsof) {
52-
port = parseInt(portFromLsof[1], 10)
62+
// Fallback: use lsof to find the listening port
63+
try {
64+
const processInfo = await execAsync(`lsof -Pan -p ${pid} -i 2>/dev/null || true`)
65+
const portFromLsof = processInfo.stdout.match(/:(\d+).*LISTEN/)
66+
if (portFromLsof) {
67+
port = parseInt(portFromLsof[1], 10)
68+
}
69+
} catch {
70+
// If lsof fails, continue with default port
5371
}
5472
}
5573

56-
servers.push({ port, pid, command })
74+
// Avoid duplicate ports (in case multiple processes are detected)
75+
if (!seenPorts.has(port)) {
76+
seenPorts.add(port)
77+
servers.push({ port, pid, command })
78+
}
5779
}
5880
}
5981

@@ -188,6 +210,55 @@ export async function callNextJsTool(
188210
}
189211
}
190212

191-
export async function getAllAvailableServers(): Promise<NextJsServerInfo[]> {
192-
return findNextJsServers()
213+
/**
214+
* Verify if a server has MCP endpoint available
215+
*/
216+
async function verifyMCPEndpoint(port: number): Promise<boolean> {
217+
try {
218+
const url = `http://localhost:${port}/_next/mcp`
219+
const controller = new AbortController()
220+
const timeoutId = setTimeout(() => controller.abort(), 1000) // 1 second timeout
221+
222+
const response = await fetch(url, {
223+
method: "POST",
224+
headers: {
225+
"Content-Type": "application/json",
226+
"Accept": "application/json, text/event-stream",
227+
},
228+
body: JSON.stringify({
229+
jsonrpc: "2.0",
230+
method: "tools/list",
231+
params: {},
232+
id: 1,
233+
}),
234+
signal: controller.signal,
235+
})
236+
237+
clearTimeout(timeoutId)
238+
return response.ok || response.status === 200
239+
} catch {
240+
return false
241+
}
242+
}
243+
244+
export async function getAllAvailableServers(verifyMCP: boolean = true): Promise<NextJsServerInfo[]> {
245+
const servers = await findNextJsServers()
246+
247+
if (!verifyMCP) {
248+
return servers
249+
}
250+
251+
// Filter servers that actually have MCP enabled
252+
const verifiedServers: NextJsServerInfo[] = []
253+
254+
await Promise.all(
255+
servers.map(async (server) => {
256+
const hasMCP = await verifyMCPEndpoint(server.port)
257+
if (hasMCP) {
258+
verifiedServers.push(server)
259+
}
260+
})
261+
)
262+
263+
return verifiedServers
193264
}

src/mcp-tools/nextjs-runtime.ts

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ const nextjsRuntimeInputSchema = z.object({
1111
action: z
1212
.enum(["discover_servers", "list_tools", "call_tool"])
1313
.describe(
14-
"Action to perform: 'discover_servers' finds running Next.js servers, 'list_tools' lists available MCP tools from the Next.js runtime, 'call_tool' calls a specific tool"
14+
"Action to perform:\n" +
15+
"- 'discover_servers': Find and list all running Next.js dev servers (use for queries about 'how many', 'show all', 'list', 'running servers')\n" +
16+
"- 'list_tools': Show available MCP tools/functions from a specific Next.js server (use after discovering servers)\n" +
17+
"- 'call_tool': Execute a specific Next.js runtime tool (use to interact with Next.js internals)"
1518
),
1619

1720
port: z
@@ -34,10 +37,33 @@ const nextjsRuntimeInputSchema = z.object({
3437
.describe(
3538
"Arguments object to pass to the Next.js MCP tool. MUST be an object (e.g., {param: 'value'}), NOT a string. Only provide this parameter if the tool requires arguments - omit it entirely for tools that take no arguments. Use 'list_tools' to see the inputSchema for each tool."
3639
),
40+
41+
includeUnverified: z
42+
.boolean()
43+
.optional()
44+
.describe(
45+
"For 'discover_servers' action: Include Next.js servers even if MCP endpoint verification fails. Defaults to false (only show verified MCP-enabled servers)."
46+
),
3747
})
3848

3949
export const nextjsRuntimeTool = tool({
40-
description: `Interact with a running Next.js development server's MCP endpoint.
50+
description: `Discover, inspect, and interact with running Next.js development servers and their MCP endpoints.
51+
52+
USE THIS TOOL WHEN:
53+
- User asks about running Next.js servers (e.g., "how many Next.js apps are running?", "show me running servers", "list Next.js apps")
54+
- User wants to see what servers/apps are active
55+
- User wants to interact with Next.js runtime capabilities
56+
- User needs to call Next.js internal tools or APIs
57+
- User asks about server ports, PIDs, or processes
58+
59+
COMMON QUERY EXAMPLES:
60+
✓ "How many Next.js apps with MCP are running?"
61+
✓ "Show me all running Next.js servers"
62+
✓ "List running Next.js development servers"
63+
✓ "What Next.js apps are currently active?"
64+
✓ "Are there any Next.js servers running?"
65+
✓ "What tools are available in the Next.js runtime?"
66+
✓ "Clear the Next.js cache"
4167
4268
REQUIREMENTS:
4369
- Next.js 16 or later (MCP support was added in v16)
@@ -47,12 +73,12 @@ Next.js exposes an MCP (Model Context Protocol) endpoint at /_next/mcp when star
4773
- experimental.mcpServer: true in next.config.js, OR
4874
- __NEXT_EXPERIMENTAL_MCP_SERVER=true environment variable
4975
50-
This tool allows you to:
51-
1. Discover running Next.js dev servers and their ports
52-
2. List available MCP tools/functions exposed by the Next.js runtime
53-
3. Call those tools to interact with Next.js internals (e.g., get route info, clear cache, etc.)
76+
CAPABILITIES:
77+
1. Discover running Next.js dev servers and their ports (action='discover_servers')
78+
2. List available MCP tools/functions exposed by the Next.js runtime (action='list_tools')
79+
3. Call those tools to interact with Next.js internals (action='call_tool')
5480
55-
Typical workflow:
81+
TYPICAL WORKFLOW:
5682
1. Use action='discover_servers' to find running Next.js servers
5783
2. Use action='list_tools' with the discovered port to see available tools and their input schemas
5884
3. Use action='call_tool' with port, toolName, and args (as an object, only if required) to invoke a specific tool
@@ -70,24 +96,34 @@ If the MCP endpoint is not available:
7096
try {
7197
switch (args.action) {
7298
case "discover_servers": {
73-
const servers = await getAllAvailableServers()
99+
const verifyMCP = !args.includeUnverified
100+
const servers = await getAllAvailableServers(verifyMCP)
74101

75102
if (servers.length === 0) {
76103
return JSON.stringify({
77104
success: false,
78-
message: "No running Next.js dev servers found",
105+
message: verifyMCP
106+
? "No running Next.js dev servers with MCP enabled found"
107+
: "No running Next.js dev servers found",
79108
hint: "Start a Next.js dev server with __NEXT_EXPERIMENTAL_MCP_SERVER=true or experimental.mcpServer: true",
109+
count: 0,
80110
})
81111
}
82112

83113
return JSON.stringify({
84114
success: true,
115+
count: servers.length,
85116
servers: servers.map((s) => ({
86117
port: s.port,
87118
pid: s.pid,
88119
command: s.command,
120+
url: `http://localhost:${s.port}`,
121+
mcpEndpoint: `http://localhost:${s.port}/_next/mcp`,
89122
})),
90-
message: `Found ${servers.length} Next.js server(s)`,
123+
message: verifyMCP
124+
? `Found ${servers.length} Next.js server${servers.length === 1 ? '' : 's'} running with MCP support`
125+
: `Found ${servers.length} Next.js server${servers.length === 1 ? '' : 's'} running (MCP verification skipped)`,
126+
summary: servers.map((s) => `Server on port ${s.port} (PID: ${s.pid})`).join('\n'),
91127
})
92128
}
93129

0 commit comments

Comments
 (0)