(target: Target, runDir?: string)
| 283 | }; |
| 284 | |
| 285 | export const makeMcpSurface = (target: Target, runDir?: string): McpSurface => ({ |
| 286 | url: target.mcpUrl, |
| 287 | mintBearer: (email) => Effect.promise(() => mintBearerFlow(target, email)), |
| 288 | session: (identity, options) => { |
| 289 | const mcpUrl = options?.url ?? target.mcpUrl; |
| 290 | if (runDir) installTraceparentFetch(mcpUrl, runDir); |
| 291 | // mcporter caches OAuth tokens (and the DCR client) per server NAME, so a |
| 292 | // constant name would let a later session reuse an earlier identity's token |
| 293 | // — landing in the wrong org. A unique name per session keeps each |
| 294 | // identity's OAuth isolated. The traceparent ledger keys off the URL, not |
| 295 | // this name, so it is unaffected. |
| 296 | const serverName = `${target.name}-${randomUUID().slice(0, 8)}`; |
| 297 | // `browser` mode is selected per the ecosystem convention — an |
| 298 | // `?elicitation_mode=` query on the MCP endpoint — so a paused execution |
| 299 | // yields an approvalUrl instead of letting the model resume inline. |
| 300 | const sessionUrl = options?.elicitationMode |
| 301 | ? `${mcpUrl}?elicitation_mode=${options.elicitationMode}` |
| 302 | : mcpUrl; |
| 303 | |
| 304 | if (target.name === "cloudflare") { |
| 305 | let clientPromise: Promise<Client> | undefined; |
| 306 | const client = () => { |
| 307 | if (!clientPromise) { |
| 308 | clientPromise = (async () => { |
| 309 | const directClient = new Client( |
| 310 | { name: serverName, version: "1.0.0" }, |
| 311 | { capabilities: {} }, |
| 312 | ); |
| 313 | const transport = new StreamableHTTPClientTransport(new URL(sessionUrl), { |
| 314 | requestInit: { headers: identity.headers ?? {} }, |
| 315 | }); |
| 316 | await directClient.connect(transport); |
| 317 | return directClient; |
| 318 | })(); |
| 319 | } |
| 320 | return clientPromise; |
| 321 | }; |
| 322 | |
| 323 | const listTools = () => |
| 324 | Effect.promise(async () => { |
| 325 | const listed = await (await client()).listTools(); |
| 326 | return listed.tools.map((tool) => tool.name); |
| 327 | }); |
| 328 | |
| 329 | const call = (name: string, args: Record<string, unknown> = {}) => |
| 330 | Effect.promise(async (): Promise<McpCallResult> => { |
| 331 | const raw = await (await client()).callTool({ name, arguments: args }); |
| 332 | const isError = Boolean((raw as { isError?: boolean })?.isError); |
| 333 | return { raw, text: textOf(raw), ok: !isError }; |
| 334 | }); |
| 335 | |
| 336 | return { |
| 337 | listTools, |
| 338 | describeTools: () => |
| 339 | Effect.promise(async (): Promise<ReadonlyArray<McpToolDef>> => { |
| 340 | const listed = await (await client()).listTools(); |
| 341 | return listed.tools.map((tool) => ({ |
| 342 | name: tool.name, |
no test coverage detected