mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-19 21:00:40 +08:00
Fix HEAD route validation and IPv6 localhost detection
- HEAD now delegates to handleMcpToolRoute so it returns 404 for unknown tool routes (matching GET behavior per HTTP spec). - isLocalHostname accepts bracketed [::1] form returned by URL.hostname. - Removed unused handleMcpToolHead export. - Updated route and wrapper tests to cover HEAD 404 case. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
19705a3970
commit
8d9771431d
@ -3,18 +3,41 @@ import { describe, expect, it, vi } from "vitest";
|
||||
import { HEAD } from "./route";
|
||||
|
||||
describe("skill-site MCP tool route", () => {
|
||||
it("does not call MCP tools for HEAD requests", () => {
|
||||
it("does not call MCP tools for HEAD requests but validates the route", async () => {
|
||||
const previousFetch = globalThis.fetch;
|
||||
const fetchMock = vi.fn();
|
||||
const previousHexclaveMcpBaseUrl = process.env.HEXCLAVE_MCP_BASE_URL;
|
||||
process.env.HEXCLAVE_MCP_BASE_URL = "https://mcp.hexclave.com/mcp";
|
||||
|
||||
const fetchMock = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const body = typeof init?.body === "string" ? JSON.parse(init.body) : null;
|
||||
expect(body?.method).toBe("tools/list");
|
||||
|
||||
return new Response(`data: ${JSON.stringify({
|
||||
result: {
|
||||
tools: [{ name: "ask_hexclave", inputSchema: null }],
|
||||
},
|
||||
jsonrpc: "2.0",
|
||||
id: 1,
|
||||
})}`);
|
||||
});
|
||||
|
||||
globalThis.fetch = fetchMock;
|
||||
|
||||
try {
|
||||
const response = HEAD();
|
||||
const found = await HEAD(new Request("https://skill.hexclave.com/ask", { method: "HEAD" }));
|
||||
expect(found.status).toBe(200);
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
fetchMock.mockClear();
|
||||
const notFound = await HEAD(new Request("https://skill.hexclave.com/nonexistent", { method: "HEAD" }));
|
||||
expect(notFound.status).toBe(404);
|
||||
} finally {
|
||||
globalThis.fetch = previousFetch;
|
||||
if (previousHexclaveMcpBaseUrl == null) {
|
||||
delete process.env.HEXCLAVE_MCP_BASE_URL;
|
||||
} else {
|
||||
process.env.HEXCLAVE_MCP_BASE_URL = previousHexclaveMcpBaseUrl;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { handleMcpToolHead, handleMcpToolOptions, handleMcpToolRoute } from "@/mcp-wrapper";
|
||||
import { handleMcpToolOptions, handleMcpToolRoute } from "@/mcp-wrapper";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@ -6,8 +6,8 @@ export async function GET(req: Request) {
|
||||
return await handleMcpToolRoute(req);
|
||||
}
|
||||
|
||||
export function HEAD() {
|
||||
return handleMcpToolHead();
|
||||
export async function HEAD(req: Request) {
|
||||
return await handleMcpToolRoute(req);
|
||||
}
|
||||
|
||||
export function OPTIONS() {
|
||||
|
||||
@ -173,4 +173,30 @@ describe("skill-site MCP wrapper", () => {
|
||||
restoreEnvVariable("HEXCLAVE_MCP_BASE_URL", previousHexclaveMcpBaseUrl);
|
||||
}
|
||||
});
|
||||
|
||||
it("returns 404 for HEAD requests to unknown tool routes", async () => {
|
||||
const previousFetch = globalThis.fetch;
|
||||
const previousHexclaveMcpBaseUrl = process.env.HEXCLAVE_MCP_BASE_URL;
|
||||
process.env.HEXCLAVE_MCP_BASE_URL = "https://mcp.hexclave.com/mcp";
|
||||
|
||||
const fetchMock = vi.fn(async () => {
|
||||
return new Response(`data: ${JSON.stringify({
|
||||
result: {
|
||||
tools: [askTool],
|
||||
},
|
||||
jsonrpc: "2.0",
|
||||
id: 1,
|
||||
})}`);
|
||||
});
|
||||
|
||||
globalThis.fetch = fetchMock;
|
||||
|
||||
try {
|
||||
const response = await handleMcpToolRoute(new Request("https://skill.hexclave.com/nonexistent", { method: "HEAD" }));
|
||||
expect(response.status).toBe(404);
|
||||
} finally {
|
||||
globalThis.fetch = previousFetch;
|
||||
restoreEnvVariable("HEXCLAVE_MCP_BASE_URL", previousHexclaveMcpBaseUrl);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -86,7 +86,7 @@ function getConfiguredMcpEndpointUrl(): URL | null {
|
||||
}
|
||||
|
||||
function isLocalHostname(hostname: string): boolean {
|
||||
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname.endsWith(".localhost");
|
||||
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname === "::1" || hostname.endsWith(".localhost");
|
||||
}
|
||||
|
||||
function getSiblingMcpUrl(req: Request): URL {
|
||||
@ -455,6 +455,4 @@ export function handleMcpToolOptions(): Response {
|
||||
});
|
||||
}
|
||||
|
||||
export function handleMcpToolHead(): Response {
|
||||
return textResponse("");
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user