Skip to content

Commit 37bcf17

Browse files
authored
Fix Next.js runtime discovery on Windows (#67)
1 parent 7d65104 commit 37bcf17

File tree

14 files changed

+376
-49
lines changed

14 files changed

+376
-49
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ on:
1010

1111
jobs:
1212
test:
13-
runs-on: ubuntu-latest
13+
runs-on: ${{ matrix.os }}
14+
strategy:
15+
matrix:
16+
os: [ubuntu-latest, windows-latest]
1417
steps:
1518
- name: Checkout code
1619
uses: actions/checkout@v4

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,8 @@ test/e2e/.env
1414
_*.md
1515
.mcpregistry*
1616

17+
# Test fixtures
18+
test/fixtures/**/pnpm-lock.yaml
19+
test/fixtures/**/node_modules/
20+
test/fixtures/**/.next/
21+

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
},
3434
"dependencies": {
3535
"@modelcontextprotocol/sdk": "1.20.0",
36+
"find-process": "^2.0.0",
3637
"xmcp": "^0.3.5",
3738
"zod": "^3.25.76"
3839
},

pnpm-lock.yaml

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/_internal/nextjs-runtime-manager.ts

Lines changed: 17 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
import { exec } from "child_process"
2-
import { promisify } from "util"
3-
4-
const execAsync = promisify(exec)
1+
import find from "find-process"
52

63
interface NextJsServerInfo {
74
port: number
@@ -30,52 +27,35 @@ interface NextJsMCPResponse {
3027

3128
async function findNextJsServers(): Promise<NextJsServerInfo[]> {
3229
try {
33-
const { stdout } = await execAsync("ps aux")
34-
const lines = stdout.split("\n")
30+
const nodeProcesses = await find("name", "node", true)
3531
const servers: NextJsServerInfo[] = []
3632
const seenPorts = new Set<number>()
3733

38-
for (const line of lines) {
39-
// Enhanced detection patterns for Next.js servers
34+
for (const proc of nodeProcesses) {
35+
const command = proc.cmd || ""
36+
4037
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"))
38+
command.includes("next dev") ||
39+
command.includes("next-server") ||
40+
command.includes("next/dist/bin/next") ||
41+
(command.includes("next") && command.includes("dev"))
4542

4643
if (isNextJsProcess) {
47-
const parts = line.trim().split(/\s+/)
48-
const pid = parseInt(parts[1], 10)
49-
const command = parts.slice(10).join(" ")
50-
51-
// Try multiple methods to detect port
5244
const portMatch =
5345
command.match(/--port[=\s]+(\d+)/) ||
5446
command.match(/-p[=\s]+(\d+)/) ||
47+
command.match(/--port\s+["'](\d+)["']/) ||
48+
command.match(/["']--port["']\s+["']?(\d+)["']?/) ||
49+
command.match(/["'](\d+)["']\s*$/) ||
5550
command.match(/:(\d+)/)
5651

57-
let port = 3000 // Default Next.js port
58-
5952
if (portMatch) {
60-
port = parseInt(portMatch[1], 10)
61-
} else {
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
53+
const port = parseInt(portMatch[1], 10)
54+
if (!seenPorts.has(port)) {
55+
seenPorts.add(port)
56+
servers.push({ port, pid: proc.pid, command })
7157
}
7258
}
73-
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-
}
7959
}
8060
}
8161

@@ -159,22 +139,13 @@ async function makeNextJsMCPRequest(
159139
}
160140
}
161141

162-
export async function discoverNextJsServer(
163-
preferredPort?: number
164-
): Promise<NextJsServerInfo | null> {
142+
export async function discoverNextJsServer(): Promise<NextJsServerInfo | null> {
165143
const servers = await findNextJsServers()
166144

167145
if (servers.length === 0) {
168146
return null
169147
}
170148

171-
if (preferredPort) {
172-
const server = servers.find((s) => s.port === preferredPort)
173-
if (server) {
174-
return server
175-
}
176-
}
177-
178149
if (servers.length === 1) {
179150
return servers[0]
180151
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
.next
3+
.env*.local
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export const metadata = {
2+
title: 'Next.js 16 Test Fixture',
3+
description: 'Minimal Next.js 16 app for testing',
4+
}
5+
6+
export default function RootLayout({
7+
children,
8+
}: {
9+
children: React.ReactNode
10+
}) {
11+
return (
12+
<html lang="en">
13+
<body>{children}</body>
14+
</html>
15+
)
16+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default function Home() {
2+
return (
3+
<main>
4+
<h1>Next.js 16 Minimal Test Fixture</h1>
5+
<p>This is a minimal Next.js 16 app for testing.</p>
6+
</main>
7+
)
8+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/// <reference types="next" />
2+
/// <reference types="next/image-types/global" />
3+
import "./.next/dev/types/routes.d.ts";
4+
5+
// NOTE: This file should not be edited
6+
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import type { NextConfig } from 'next'
2+
3+
const nextConfig: NextConfig = {}
4+
5+
export default nextConfig

0 commit comments

Comments
 (0)