Merge pull request #1643 from code-yeongyu/fix/exa-api-key-1627
fix(mcp): append EXA_API_KEY to Exa MCP URL when env var is set (#1627)
This commit is contained in:
@@ -1,16 +1,30 @@
|
||||
import { describe, expect, test, beforeEach, afterEach } from "bun:test"
|
||||
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
||||
import { createWebsearchConfig } from "./websearch"
|
||||
|
||||
describe("websearch MCP provider configuration", () => {
|
||||
const originalEnv = { ...process.env }
|
||||
let originalExaApiKey: string | undefined
|
||||
let originalTavilyApiKey: string | undefined
|
||||
|
||||
beforeEach(() => {
|
||||
originalExaApiKey = process.env.EXA_API_KEY
|
||||
originalTavilyApiKey = process.env.TAVILY_API_KEY
|
||||
|
||||
delete process.env.EXA_API_KEY
|
||||
delete process.env.TAVILY_API_KEY
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
process.env = { ...originalEnv }
|
||||
if (originalExaApiKey === undefined) {
|
||||
delete process.env.EXA_API_KEY
|
||||
} else {
|
||||
process.env.EXA_API_KEY = originalExaApiKey
|
||||
}
|
||||
|
||||
if (originalTavilyApiKey === undefined) {
|
||||
delete process.env.TAVILY_API_KEY
|
||||
} else {
|
||||
process.env.TAVILY_API_KEY = originalTavilyApiKey
|
||||
}
|
||||
})
|
||||
|
||||
test("returns Exa config when no config provided", () => {
|
||||
@@ -21,6 +35,7 @@ describe("websearch MCP provider configuration", () => {
|
||||
|
||||
//#then
|
||||
expect(result.url).toContain("mcp.exa.ai")
|
||||
expect(result.url).toContain("tools=web_search_exa")
|
||||
expect(result.type).toBe("remote")
|
||||
expect(result.enabled).toBe(true)
|
||||
})
|
||||
@@ -34,10 +49,11 @@ describe("websearch MCP provider configuration", () => {
|
||||
|
||||
//#then
|
||||
expect(result.url).toContain("mcp.exa.ai")
|
||||
expect(result.url).toContain("tools=web_search_exa")
|
||||
expect(result.type).toBe("remote")
|
||||
})
|
||||
|
||||
test("includes x-api-key header when EXA_API_KEY is set", () => {
|
||||
test("appends exaApiKey query param when EXA_API_KEY is set", () => {
|
||||
//#given
|
||||
const apiKey = "test-exa-key-12345"
|
||||
process.env.EXA_API_KEY = apiKey
|
||||
@@ -46,7 +62,30 @@ describe("websearch MCP provider configuration", () => {
|
||||
const result = createWebsearchConfig()
|
||||
|
||||
//#then
|
||||
expect(result.headers).toEqual({ "x-api-key": apiKey })
|
||||
expect(result.url).toContain(`exaApiKey=${encodeURIComponent(apiKey)}`)
|
||||
})
|
||||
|
||||
test("does not set x-api-key header when EXA_API_KEY is set", () => {
|
||||
//#given
|
||||
process.env.EXA_API_KEY = "test-exa-key-12345"
|
||||
|
||||
//#when
|
||||
const result = createWebsearchConfig()
|
||||
|
||||
//#then
|
||||
expect(result.headers).toBeUndefined()
|
||||
})
|
||||
|
||||
test("URL-encodes EXA_API_KEY when it contains special characters", () => {
|
||||
//#given an EXA_API_KEY with special characters (+ & =)
|
||||
const apiKey = "a+b&c=d"
|
||||
process.env.EXA_API_KEY = apiKey
|
||||
|
||||
//#when createWebsearchConfig is called
|
||||
const result = createWebsearchConfig()
|
||||
|
||||
//#then the URL contains the properly encoded key via encodeURIComponent
|
||||
expect(result.url).toContain(`exaApiKey=${encodeURIComponent(apiKey)}`)
|
||||
})
|
||||
|
||||
test("returns Tavily config when provider is 'tavily' and TAVILY_API_KEY set", () => {
|
||||
@@ -77,7 +116,8 @@ describe("websearch MCP provider configuration", () => {
|
||||
|
||||
test("returns Exa when both keys present but no explicit provider", () => {
|
||||
//#given
|
||||
process.env.EXA_API_KEY = "test-exa-key"
|
||||
const exaKey = "test-exa-key"
|
||||
process.env.EXA_API_KEY = exaKey
|
||||
process.env.TAVILY_API_KEY = "test-tavily-key"
|
||||
|
||||
//#when
|
||||
@@ -85,7 +125,8 @@ describe("websearch MCP provider configuration", () => {
|
||||
|
||||
//#then
|
||||
expect(result.url).toContain("mcp.exa.ai")
|
||||
expect(result.headers).toEqual({ "x-api-key": "test-exa-key" })
|
||||
expect(result.url).toContain(`exaApiKey=${encodeURIComponent(exaKey)}`)
|
||||
expect(result.headers).toBeUndefined()
|
||||
})
|
||||
|
||||
test("Tavily config uses Authorization Bearer header format", () => {
|
||||
@@ -111,6 +152,8 @@ describe("websearch MCP provider configuration", () => {
|
||||
|
||||
//#then
|
||||
expect(result.url).toContain("mcp.exa.ai")
|
||||
expect(result.url).toContain("tools=web_search_exa")
|
||||
expect(result.url).not.toContain("exaApiKey=")
|
||||
expect(result.headers).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -31,11 +31,10 @@ export function createWebsearchConfig(config?: WebsearchConfig): RemoteMcpConfig
|
||||
// Default to Exa
|
||||
return {
|
||||
type: "remote" as const,
|
||||
url: "https://mcp.exa.ai/mcp?tools=web_search_exa",
|
||||
url: process.env.EXA_API_KEY
|
||||
? `https://mcp.exa.ai/mcp?tools=web_search_exa&exaApiKey=${encodeURIComponent(process.env.EXA_API_KEY)}`
|
||||
: "https://mcp.exa.ai/mcp?tools=web_search_exa",
|
||||
enabled: true,
|
||||
headers: process.env.EXA_API_KEY
|
||||
? { "x-api-key": process.env.EXA_API_KEY }
|
||||
: undefined,
|
||||
oauth: false as const,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user