refactor(mcp): use Effect-native catch + tryPromise instead of try/catch

Per CLAUDE.md style guide ("Avoid try/catch where possible") and the
opencode Effect rules ("Use Effect.tryPromise for promise-based APIs",
"Use Effect.fnUntraced for internal helpers"), replace the plain
async function + try/catch in listTools with an Effect-native
listToolsTolerant that composes via Effect.tryPromise + Effect.catch.

Also drops the no-op `Effect.map((tools) => tools)` identity in defs(),
and extracts a tiny `wrapAsError` helper to remove the duplicated
"err instanceof Error ? ... : new Error(String(err))" expression.

No behavior change. 20/20 tests still green.
This commit is contained in:
Developer
2026-05-09 19:15:36 -04:00
parent d3a69ad910
commit ff6f032faa
+22 -21
View File
@@ -137,25 +137,30 @@ function remoteURL(key: string, value: string) {
log.warn("invalid remote mcp url", { key }) log.warn("invalid remote mcp url", { key })
} }
function isSchemaReferenceError(err: Error) { function isSchemaReferenceError(err: unknown) {
return /can't resolve reference|schema.*reference|reference.*schema/i.test(err.message) return err instanceof Error && /can't resolve reference|schema.*reference|reference.*schema/i.test(err.message)
} }
async function listTools(key: string, client: MCPClient, timeout: number) { const wrapAsError = (err: unknown) => (err instanceof Error ? err : new Error(String(err)))
try {
return (await client.listTools(undefined, { timeout })).tools
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err))
if (!isSchemaReferenceError(error)) throw error
log.warn("failed to validate MCP tool output schemas, retrying without output schema validation", { function listToolsTolerant(key: string, client: MCPClient, timeout: number) {
key, return Effect.tryPromise({
error, try: () => client.listTools(undefined, { timeout }),
}) catch: wrapAsError,
}).pipe(
const result = await client.request({ method: "tools/list" }, TolerantListToolsResultSchema, { timeout }) Effect.map((result) => result.tools),
return result.tools as MCPToolDef[] Effect.catch((err) => {
} if (!isSchemaReferenceError(err)) return Effect.fail(err)
log.warn("failed to validate MCP tool output schemas, retrying without output schema validation", {
key,
error: err,
})
return Effect.tryPromise({
try: () => client.request({ method: "tools/list" }, TolerantListToolsResultSchema, { timeout }),
catch: wrapAsError,
}).pipe(Effect.map((result) => result.tools as MCPToolDef[]))
}),
)
} }
// Convert MCP tool definition to AI SDK Tool type // Convert MCP tool definition to AI SDK Tool type
@@ -190,11 +195,7 @@ function convertMcpTool(mcpTool: MCPToolDef, client: MCPClient, timeout?: number
} }
function defs(key: string, client: MCPClient, timeout?: number) { function defs(key: string, client: MCPClient, timeout?: number) {
return Effect.tryPromise({ return listToolsTolerant(key, client, timeout ?? DEFAULT_TIMEOUT).pipe(
try: () => listTools(key, client, timeout ?? DEFAULT_TIMEOUT),
catch: (err) => (err instanceof Error ? err : new Error(String(err))),
}).pipe(
Effect.map((tools) => tools),
Effect.catch((err) => { Effect.catch((err) => {
log.error("failed to get tools from client", { key, error: err }) log.error("failed to get tools from client", { key, error: err })
return Effect.succeed(undefined) return Effect.succeed(undefined)