Skip to content

Commit 2f26f0e

Browse files
fix(docs): correct MCP server routing to resolve 404 errors (#4673)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Deep Singhvi <[email protected]>
1 parent bb8cc8b commit 2f26f0e

File tree

2 files changed

+150
-1
lines changed

2 files changed

+150
-1
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { NextRequest } from "next/server";
2+
import { beforeEach, describe, expect, it, vi } from "vitest";
3+
import { GET, POST } from "./route";
4+
5+
vi.mock("@fern-api/docs-loader", () => ({
6+
getMetadata: vi.fn(
7+
() => () =>
8+
Promise.resolve({
9+
org: "test-org"
10+
})
11+
)
12+
}));
13+
14+
vi.mock("@fern-api/docs-server/auth/getAuthStateEdge", () => ({
15+
createGetAuthStateEdge: vi.fn(() =>
16+
Promise.resolve({
17+
getAuthState: vi.fn(() => Promise.resolve({ ok: true }))
18+
})
19+
)
20+
}));
21+
22+
vi.mock("mcp-handler", () => ({
23+
createMcpHandler: vi.fn(() => {
24+
return async (request: NextRequest) => {
25+
return new Response(JSON.stringify({ result: "success" }), {
26+
status: 200,
27+
headers: { "Content-Type": "application/json" }
28+
});
29+
};
30+
})
31+
}));
32+
33+
describe("MCP Server Route", () => {
34+
beforeEach(() => {
35+
vi.clearAllMocks();
36+
});
37+
38+
describe("GET /_mcp/server", () => {
39+
it("should return 200 for authenticated requests with JSON-RPC params", async () => {
40+
const request = new NextRequest("http://docs.ada.cx/_mcp/server?method=initialize", {
41+
method: "GET",
42+
headers: {
43+
"Content-Type": "application/json"
44+
}
45+
});
46+
47+
const params = Promise.resolve({
48+
host: "docs.ada.cx",
49+
domain: "docs.ada.cx",
50+
lang: "en"
51+
});
52+
53+
const response = await GET(request, { params });
54+
55+
expect(response.status).toBe(200);
56+
});
57+
58+
it("should return 200 for authenticated requests with Accept: application/json", async () => {
59+
const request = new NextRequest("http://docs.ada.cx/_mcp/server", {
60+
method: "GET",
61+
headers: {
62+
Accept: "application/json"
63+
}
64+
});
65+
66+
const params = Promise.resolve({
67+
host: "docs.ada.cx",
68+
domain: "docs.ada.cx",
69+
lang: "en"
70+
});
71+
72+
const response = await GET(request, { params });
73+
74+
expect(response.status).toBe(200);
75+
});
76+
77+
it("should return 200 with plain text for non-JSON requests", async () => {
78+
const request = new NextRequest("http://docs.ada.cx/_mcp/server", {
79+
method: "GET"
80+
});
81+
82+
const params = Promise.resolve({
83+
host: "docs.ada.cx",
84+
domain: "docs.ada.cx",
85+
lang: "en"
86+
});
87+
88+
const response = await GET(request, { params });
89+
90+
expect(response.status).toBe(200);
91+
expect(response.headers.get("Content-Type")).toBe("text/plain");
92+
const text = await response.text();
93+
expect(text).toContain("mcp server");
94+
});
95+
});
96+
97+
describe("POST /_mcp/server", () => {
98+
it("should return 200 for authenticated POST requests", async () => {
99+
const request = new NextRequest("http://docs.ada.cx/_mcp/server", {
100+
method: "POST",
101+
headers: {
102+
"Content-Type": "application/json"
103+
},
104+
body: JSON.stringify({
105+
jsonrpc: "2.0",
106+
method: "initialize",
107+
params: {},
108+
id: 1
109+
})
110+
});
111+
112+
const params = Promise.resolve({
113+
host: "docs.ada.cx",
114+
domain: "docs.ada.cx",
115+
lang: "en"
116+
});
117+
118+
const response = await POST(request, { params });
119+
120+
expect(response.status).toBe(200);
121+
});
122+
123+
it("should not return 404", async () => {
124+
const request = new NextRequest("http://docs.ada.cx/_mcp/server", {
125+
method: "POST",
126+
headers: {
127+
"Content-Type": "application/json"
128+
},
129+
body: JSON.stringify({
130+
jsonrpc: "2.0",
131+
method: "tools/list",
132+
params: {},
133+
id: 2
134+
})
135+
});
136+
137+
const params = Promise.resolve({
138+
host: "docs.ada.cx",
139+
domain: "docs.ada.cx",
140+
lang: "en"
141+
});
142+
143+
const response = await POST(request, { params });
144+
145+
expect(response.status).not.toBe(404);
146+
expect(response.status).toBe(200);
147+
});
148+
});
149+
});

packages/fern-docs/bundle/src/middleware.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ export const middleware: NextMiddleware = async (request) => {
176176
* Rewrite mcp
177177
*/
178178
if (pathname.endsWith("/_mcp/server")) {
179-
return rewrite(withDomain("/api/fern-docs/mcp"));
179+
return rewrite(withDomain("/fern-docs/mcp"));
180180
}
181181

182182
/**

0 commit comments

Comments
 (0)