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:
armaan 2026-06-10 18:18:10 +00:00
parent 19705a3970
commit 8d9771431d
4 changed files with 59 additions and 12 deletions

View File

@ -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;
}
}
});
});

View File

@ -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() {

View File

@ -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);
}
});
});

View File

@ -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("");
}