Skip to content

Commit e49daf0

Browse files
committed
chore: enhance MCP resolution logic by implementing directory traversal for .browser-echo-mcp.json discovery in Next.js, Nuxt.js, and Vite, improving project-local configuration handling
1 parent e3fe112 commit e49daf0

4 files changed

Lines changed: 64 additions & 40 deletions

File tree

packages/next/src/route.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,17 +78,24 @@ function dim(s: string) { return c.dim + s + c.reset; }
7878
async function __resolveMcpFromProject(): Promise<{ url: string; routeLogs?: `/${string}` }> {
7979
try {
8080
const { readFileSync, existsSync } = await import('node:fs');
81-
const { join } = await import('node:path');
82-
const p = join(process.cwd(), '.browser-echo-mcp.json');
83-
if (existsSync(p)) {
84-
const raw = readFileSync(p, 'utf-8');
85-
const data = JSON.parse(raw);
86-
const rawUrl = (data?.url ? String(data.url) : '');
87-
const url = rawUrl.replace(/\/$/, '').replace(/\/mcp$/i, '');
88-
const routeLogs = (data?.route ? String(data.route) : '/__client-logs') as `/${string}`;
89-
if (url && await __pingHealth(`${url}/health`, 250)) {
90-
return { url, routeLogs };
81+
const { join, dirname } = await import('node:path');
82+
let dir = process.cwd();
83+
for (let depth = 0; depth < 10; depth++) {
84+
const p = join(dir, '.browser-echo-mcp.json');
85+
if (existsSync(p)) {
86+
const raw = readFileSync(p, 'utf-8');
87+
const data = JSON.parse(raw);
88+
const rawUrl = (data?.url ? String(data.url) : '');
89+
const base = rawUrl.replace(/\/$/, '').replace(/\/mcp$/i, '');
90+
if (!/^(http:\/\/127\.0\.0\.1|http:\/\/localhost)/.test(base)) break;
91+
const routeLogs = (data?.route ? String(data.route) : '/__client-logs') as `/${string}`;
92+
if (base && await __pingHealth(`${base}/health`, 250)) {
93+
return { url: base, routeLogs };
94+
}
9195
}
96+
const up = dirname(dir);
97+
if (up === dir) break;
98+
dir = up;
9299
}
93100
} catch {}
94101
return { url: '' } as any;

packages/next/test/route.test.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -112,27 +112,27 @@ it('does not fall back to 5179; prints when no project JSON present', async () =
112112
}
113113
});
114114

115-
it('walks up directories to find project-local discovery file', async () => {
115+
it('walks up directories to find discovery file and forwards', async () => {
116116
const REAL_FETCH = globalThis.fetch as any;
117-
globalThis.fetch = vi.fn(async () => ({ ok: true } as any)) as any;
117+
const calls: string[] = [];
118+
globalThis.fetch = vi.fn(async (url: any) => {
119+
const u = String(url); calls.push(u);
120+
if (u.endsWith('/health')) return { ok: true } as any;
121+
return { ok: true } as any;
122+
}) as any;
118123
const oldCwd = process.cwd();
119124
const base = mkdtempSync(join(tmpdir(), 'be-next-walk-'));
120125
const sub = join(base, 'a', 'b');
121126
mkdirSync(sub, { recursive: true });
122127
try {
123-
writeFileSync(join(base, '.browser-echo-mcp.json'), JSON.stringify({ url: 'http://127.0.0.1:59998', routeLogs: '/__client-logs', timestamp: Date.now() }));
128+
writeFileSync(join(base, '.browser-echo-mcp.json'), JSON.stringify({ url: 'http://127.0.0.1:59998', route: '/__client-logs', timestamp: Date.now() }));
124129
process.chdir(sub);
125130
vi.resetModules();
126131
const mod = await import('../src/route');
127-
const i = vi.spyOn(console, 'log').mockImplementation(() => {});
128-
const w = vi.spyOn(console, 'warn').mockImplementation(() => {});
129-
const e = vi.spyOn(console, 'error').mockImplementation(() => {});
130132
const req: any = { json: async () => ({ sessionId: 'walkbeef', entries: [{ level: 'error', text: 'z' }] }) };
131133
const res: any = await mod.POST(req);
132134
expect((res as any).status).toBe(204);
133-
// With the new model, only project root is considered; printing may still happen here
134-
expect(i.mock.calls.length + w.mock.calls.length + e.mock.calls.length).toBeGreaterThan(0);
135-
i.mockRestore(); w.mockRestore(); e.mockRestore();
135+
expect(calls.some((u) => u === 'http://127.0.0.1:59998/__client-logs')).toBe(true);
136136
} finally {
137137
process.chdir(oldCwd);
138138
try { rmSync(base, { recursive: true, force: true }); } catch {}

packages/nuxt/src/runtime/server/handler.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,16 +79,23 @@ function dim(s: string) { return c.dim + s + c.reset; }
7979
async function __resolveMcpFromProjectNuxt(): Promise<{ url: string; routeLogs?: `/${string}` }> {
8080
try {
8181
const { readFileSync, existsSync } = await import('node:fs');
82-
const { join } = await import('node:path');
83-
const p = join(process.cwd(), '.browser-echo-mcp.json');
84-
if (existsSync(p)) {
85-
const raw = readFileSync(p, 'utf-8');
86-
const data = JSON.parse(raw);
87-
const url = (data?.url ? String(data.url) : '').replace(/\/$/, '').replace(/\/mcp$/i, '');
88-
const routeLogs = (data?.route ? String(data.route) as `/${string}` : '/__client-logs');
89-
if (url && await __pingHealthNuxt(`${url}/health`, 300)) {
90-
return { url, routeLogs };
82+
const { join, dirname } = await import('node:path');
83+
let dir = process.cwd();
84+
for (let depth = 0; depth < 10; depth++) {
85+
const p = join(dir, '.browser-echo-mcp.json');
86+
if (existsSync(p)) {
87+
const raw = readFileSync(p, 'utf-8');
88+
const data = JSON.parse(raw);
89+
const base = (data?.url ? String(data.url) : '').replace(/\/$/, '').replace(/\/mcp$/i, '');
90+
if (!/^(http:\/\/127\.0\.0\.1|http:\/\/localhost)/.test(base)) break;
91+
const routeLogs = (data?.route ? String(data.route) as `/${string}` : '/__client-logs');
92+
if (base && await __pingHealthNuxt(`${base}/health`, 300)) {
93+
return { url: base, routeLogs };
94+
}
9195
}
96+
const up = dirname(dir);
97+
if (up === dir) break;
98+
dir = up;
9299
}
93100
} catch {}
94101
return { url: '' } as any;

packages/vite/src/index.ts

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -121,18 +121,24 @@ function attachMiddleware(server: any, options: ResolvedOptions) {
121121

122122
function readProjectJson(): { url: string; route?: `/${string}` } | null {
123123
try {
124-
const p = joinPath(process.cwd(), '.browser-echo-mcp.json');
125-
if (existsSync(p)) {
126-
const raw = readFileSync(p, 'utf-8');
127-
let data: any;
128-
try { data = JSON.parse(raw); }
129-
catch (err: any) {
130-
try { server.config.logger.warn(`${options.tag} failed to parse .browser-echo-mcp.json: ${err?.message || err}`); } catch {}
131-
return null;
124+
let dir = process.cwd();
125+
for (let depth = 0; depth < 10; depth++) {
126+
const p = joinPath(dir, '.browser-echo-mcp.json');
127+
if (existsSync(p)) {
128+
const raw = readFileSync(p, 'utf-8');
129+
let data: any;
130+
try { data = JSON.parse(raw); }
131+
catch (err: any) {
132+
try { server.config.logger.warn(`${options.tag} failed to parse .browser-echo-mcp.json: ${err?.message || err}`); } catch {}
133+
return null;
134+
}
135+
const url = (data?.url ? String(data.url) : '').replace(/\/$/, '');
136+
const route = (data?.route ? String(data.route) : '/__client-logs') as `/${string}`;
137+
if (url && /^(http:\/\/127\.0\.0\.1|http:\/\/localhost)/.test(url)) return { url, route };
132138
}
133-
const url = (data?.url ? String(data.url) : '').replace(/\/$/, '');
134-
const route = (data?.route ? String(data.route) : '/__client-logs') as `/${string}`;
135-
if (url) return { url, route };
139+
const up = dirname(dir);
140+
if (up === dir) break;
141+
dir = up;
136142
}
137143
} catch {}
138144
return null;
@@ -175,7 +181,11 @@ function attachMiddleware(server: any, options: ResolvedOptions) {
175181
if (!payload || !Array.isArray(payload.entries)) { res.statusCode = 400; res.end('invalid payload'); return; }
176182

177183
// Mirror to MCP server if configured
178-
const targetIngest = resolvedIngest || '';
184+
let targetIngest = resolvedIngest || '';
185+
if (!targetIngest) {
186+
try { await resolveOnce(); } catch {}
187+
targetIngest = resolvedIngest || '';
188+
}
179189
if (targetIngest) {
180190
try {
181191
// do not await

0 commit comments

Comments
 (0)