Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 40 additions & 6 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,20 +418,54 @@ describe("Perplexity MCP Server", () => {
);
});

it("should handle error text parse failures", async () => {
it("should not expose JSON parse error details", async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: async () => {
throw new Error("Invalid JSON containing secret_token=abc123");
},
} as unknown as Response);

const messages = [{ role: "user", content: "test" }];

await expect(performChatCompletion(messages)).rejects.toThrow(
"Failed to parse JSON response from Perplexity API"
);
await expect(performChatCompletion(messages)).rejects.not.toThrow(
"secret_token=abc123"
);
});

it("should not expose network error details", async () => {
global.fetch = vi.fn().mockRejectedValue(
new Error("Network failure with credential=private-token")
);

const messages = [{ role: "user", content: "test" }];

await expect(performChatCompletion(messages)).rejects.toThrow(
"Network error while calling Perplexity API"
);
await expect(performChatCompletion(messages)).rejects.not.toThrow(
"credential=private-token"
);
});

it("should not expose upstream error response details", async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: false,
status: 500,
statusText: "Internal Server Error",
text: async () => {
throw new Error("Cannot read error");
},
} as unknown as Response);
text: async () => "internal_trace=abc123; account=private-tier",
} as Response);

const messages = [{ role: "user", content: "test" }];

await expect(performChatCompletion(messages)).rejects.toThrow(
"Unable to parse error response"
"Perplexity API error: 500 Internal Server Error"
);
await expect(performChatCompletion(messages)).rejects.not.toThrow(
"internal_trace=abc123"
);
});

Expand Down
17 changes: 13 additions & 4 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
UndiciRequestOptions
} from "./types.js";
import { ChatCompletionResponseSchema, SearchResponseSchema } from "./validation.js";
import { logger } from "./logger.js";

const PERPLEXITY_API_KEY = process.env.PERPLEXITY_API_KEY;
const PERPLEXITY_BASE_URL = process.env.PERPLEXITY_BASE_URL || "https://api.perplexity.ai";
Expand Down Expand Up @@ -99,7 +100,8 @@ async function makeApiRequest(
if (error instanceof Error && error.name === "AbortError") {
throw new Error(`Request timeout: Perplexity API did not respond within ${TIMEOUT_MS}ms. Consider increasing PERPLEXITY_TIMEOUT_MS.`);
}
throw new Error(`Network error while calling Perplexity API: ${error}`);
logger.error("Network error while calling Perplexity API", { error: String(error) });
throw new Error("Network error while calling Perplexity API");
}
clearTimeout(timeoutId);

Expand All @@ -110,8 +112,13 @@ async function makeApiRequest(
} catch (parseError) {
errorText = "Unable to parse error response";
}
logger.error("Perplexity API error", {
status: response.status,
statusText: response.statusText,
body: errorText,
});
throw new Error(
`Perplexity API error: ${response.status} ${response.statusText}\n${errorText}`
`Perplexity API error: ${response.status} ${response.statusText}`
);
}

Expand Down Expand Up @@ -228,7 +235,8 @@ export async function performChatCompletion(
throw new Error("Invalid API response: missing or empty choices array");
}
}
throw new Error(`Failed to parse JSON response from Perplexity API: ${error}`);
logger.error("Failed to parse JSON response from Perplexity API", { error: String(error) });
throw new Error("Failed to parse JSON response from Perplexity API");
}

const firstChoice = data.choices[0];
Expand Down Expand Up @@ -292,7 +300,8 @@ export async function performSearch(
const json = await response.json();
data = SearchResponseSchema.parse(json);
} catch (error) {
throw new Error(`Failed to parse JSON response from Perplexity Search API: ${error}`);
logger.error("Failed to parse JSON response from Perplexity Search API", { error: String(error) });
throw new Error("Failed to parse JSON response from Perplexity Search API");
}

return formatSearchResults(data);
Expand Down