fix(mcp): tolerate output schema ref failures (#26614)
This commit is contained in:
@@ -6,6 +6,7 @@ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
|
||||
import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js"
|
||||
import {
|
||||
CallToolResultSchema,
|
||||
ToolSchema,
|
||||
type Tool as MCPToolDef,
|
||||
ToolListChangedNotificationSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js"
|
||||
@@ -36,6 +37,15 @@ import { withStatics } from "@opencode-ai/core/schema"
|
||||
const log = Log.create({ service: "mcp" })
|
||||
const DEFAULT_TIMEOUT = 30_000
|
||||
|
||||
const TolerantToolSchema = ToolSchema.extend({
|
||||
outputSchema: z.unknown().optional(),
|
||||
})
|
||||
|
||||
const TolerantListToolsResultSchema = z.looseObject({
|
||||
tools: z.array(TolerantToolSchema),
|
||||
nextCursor: z.string().optional(),
|
||||
})
|
||||
|
||||
export const Resource = Schema.Struct({
|
||||
name: Schema.String,
|
||||
uri: Schema.String,
|
||||
@@ -119,6 +129,38 @@ function remoteURL(key: string, value: string) {
|
||||
log.warn("invalid remote mcp url", { key })
|
||||
}
|
||||
|
||||
function isOutputSchemaValidationError(error: Error) {
|
||||
return /can't resolve reference|resolves to more than one schema|outputSchema|schema.*reference|reference.*schema/i.test(
|
||||
error.message,
|
||||
)
|
||||
}
|
||||
|
||||
function listTools(key: string, client: MCPClient, timeout: number) {
|
||||
return Effect.tryPromise({
|
||||
try: () => client.listTools(undefined, { timeout }),
|
||||
catch: (err) => (err instanceof Error ? err : new Error(String(err))),
|
||||
}).pipe(
|
||||
Effect.map((result) => result.tools),
|
||||
Effect.catch((error) => {
|
||||
if (!isOutputSchemaValidationError(error)) return Effect.fail(error)
|
||||
|
||||
log.warn("failed to validate MCP tool output schemas, retrying without output schema validation", { key, error })
|
||||
return Effect.tryPromise({
|
||||
try: () => client.request({ method: "tools/list" }, TolerantListToolsResultSchema, { timeout }),
|
||||
catch: (err) => (err instanceof Error ? err : new Error(String(err))),
|
||||
}).pipe(
|
||||
Effect.map((result) =>
|
||||
result.tools.map((tool) => ({
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
inputSchema: tool.inputSchema,
|
||||
})),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
// Convert MCP tool definition to AI SDK Tool type
|
||||
function convertMcpTool(mcpTool: MCPToolDef, client: MCPClient, timeout?: number): Tool {
|
||||
const inputSchema = mcpTool.inputSchema
|
||||
@@ -151,11 +193,7 @@ function convertMcpTool(mcpTool: MCPToolDef, client: MCPClient, timeout?: number
|
||||
}
|
||||
|
||||
function defs(key: string, client: MCPClient, timeout?: number) {
|
||||
return Effect.tryPromise({
|
||||
try: () => withTimeout(client.listTools(), timeout ?? DEFAULT_TIMEOUT),
|
||||
catch: (err) => (err instanceof Error ? err : new Error(String(err))),
|
||||
}).pipe(
|
||||
Effect.map((result) => result.tools),
|
||||
return listTools(key, client, timeout ?? DEFAULT_TIMEOUT).pipe(
|
||||
Effect.catch((err) => {
|
||||
log.error("failed to get tools from client", { key, error: err })
|
||||
return Effect.succeed(undefined)
|
||||
|
||||
Reference in New Issue
Block a user