import { Duration, Effect, Schema } from "effect" import { HttpClient, HttpClientRequest } from "effect/unstable/http" const URL = process.env.EXA_API_KEY ? `https://mcp.exa.ai/mcp?exaApiKey=${encodeURIComponent(process.env.EXA_API_KEY)}` : "https://mcp.exa.ai/mcp" const McpResult = Schema.Struct({ result: Schema.Struct({ content: Schema.Array( Schema.Struct({ type: Schema.String, text: Schema.String, }), ), }), }) const decode = Schema.decodeUnknownEffect(Schema.fromJsonString(McpResult)) const parseSse = Effect.fn("McpExa.parseSse")(function* (body: string) { for (const line of body.split("\n")) { if (!line.startsWith("data: ")) continue const data = yield* decode(line.substring(6)) if (data.result.content[0]?.text) return data.result.content[0].text } return undefined }) export const SearchArgs = Schema.Struct({ query: Schema.String, type: Schema.String, numResults: Schema.Number, livecrawl: Schema.String, contextMaxCharacters: Schema.optional(Schema.Number), }) export const CodeArgs = Schema.Struct({ query: Schema.String, tokensNum: Schema.Number, }) const McpRequest = (args: Schema.Struct) => Schema.Struct({ jsonrpc: Schema.Literal("2.0"), id: Schema.Literal(1), method: Schema.Literal("tools/call"), params: Schema.Struct({ name: Schema.String, arguments: args, }), }) export const call = ( http: HttpClient.HttpClient, tool: string, args: Schema.Struct, value: Schema.Struct.Type, timeout: Duration.Input, ) => Effect.gen(function* () { const request = yield* HttpClientRequest.post(URL).pipe( HttpClientRequest.accept("application/json, text/event-stream"), HttpClientRequest.schemaBodyJson(McpRequest(args))({ jsonrpc: "2.0" as const, id: 1 as const, method: "tools/call" as const, params: { name: tool, arguments: value }, }), ) const response = yield* HttpClient.filterStatusOk(http) .execute(request) .pipe( Effect.timeoutOrElse({ duration: timeout, orElse: () => Effect.die(new Error(`${tool} request timed out`)) }), ) const body = yield* response.text return yield* parseSse(body) })