diff --git a/apps/skills/src/app/[toolName]/route.test.ts b/apps/skills/src/app/[toolName]/route.test.ts index 6c97acf64..4c07a1125 100644 --- a/apps/skills/src/app/[toolName]/route.test.ts +++ b/apps/skills/src/app/[toolName]/route.test.ts @@ -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; + } } }); }); diff --git a/apps/skills/src/app/[toolName]/route.ts b/apps/skills/src/app/[toolName]/route.ts index 53ae7463f..aacb3b4c7 100644 --- a/apps/skills/src/app/[toolName]/route.ts +++ b/apps/skills/src/app/[toolName]/route.ts @@ -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() { diff --git a/apps/skills/src/mcp-wrapper.test.ts b/apps/skills/src/mcp-wrapper.test.ts index 376710329..f19ddea09 100644 --- a/apps/skills/src/mcp-wrapper.test.ts +++ b/apps/skills/src/mcp-wrapper.test.ts @@ -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); + } + }); }); diff --git a/apps/skills/src/mcp-wrapper.ts b/apps/skills/src/mcp-wrapper.ts index a3c018b6e..d5cb8d06a 100644 --- a/apps/skills/src/mcp-wrapper.ts +++ b/apps/skills/src/mcp-wrapper.ts @@ -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(""); -} +