Skip to content

Commit c2cda55

Browse files
committed
fix: handle outputSchema compilation errors in MCP tool discovery
When an MCP server returns outputSchema in its tools/list response, the SDK's default AjvJsonSchemaValidator can throw during schema compilation, causing listTools() to fail entirely. This makes the server show "Failed to get tools" even though the tools themselves are valid. opencode doesn't use output schemas (only inputSchema is read in convertMcpTool), so validation failures should be non-fatal. Wrap AjvJsonSchemaValidator with a TolerantJsonSchemaValidator that catches compilation errors and returns a permissive fallback. Pass it to both Client constructors via the SDK's jsonSchemaValidator option. Closes #21373
1 parent 988c989 commit c2cda55

1 file changed

Lines changed: 27 additions & 2 deletions

File tree

packages/opencode/src/mcp/index.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
44
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"
55
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
66
import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js"
7+
import { AjvJsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/ajv"
8+
import type { jsonSchemaValidator as JsonSchemaValidatorProvider, JsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/types.js"
79
import {
810
CallToolResultSchema,
911
type Tool as MCPToolDef,
@@ -30,6 +32,29 @@ import { makeRuntime } from "@/effect/run-service"
3032
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
3133
import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner"
3234

35+
/**
36+
* Wraps AjvJsonSchemaValidator to catch schema compilation errors.
37+
* opencode doesn't validate tool output schemas, so compilation failures
38+
* (e.g. from complex or unsupported schemas) should not prevent tool discovery.
39+
*/
40+
class TolerantJsonSchemaValidator implements JsonSchemaValidatorProvider {
41+
private inner = new AjvJsonSchemaValidator()
42+
43+
getValidator<T>(schema: any): JsonSchemaValidator<T> {
44+
try {
45+
return this.inner.getValidator<T>(schema)
46+
} catch {
47+
return (input: unknown) => ({
48+
valid: true as const,
49+
data: input as T,
50+
errorMessage: undefined,
51+
})
52+
}
53+
}
54+
}
55+
56+
const tolerantValidator = new TolerantJsonSchemaValidator()
57+
3358
export namespace MCP {
3459
const log = Log.create({ service: "mcp" })
3560
const DEFAULT_TIMEOUT = 30_000
@@ -260,7 +285,7 @@ export namespace MCP {
260285
(t) =>
261286
Effect.tryPromise({
262287
try: () => {
263-
const client = new Client({ name: "opencode", version: Installation.VERSION })
288+
const client = new Client({ name: "opencode", version: Installation.VERSION }, { jsonSchemaValidator: tolerantValidator })
264289
return withTimeout(client.connect(t), timeout).then(() => client)
265290
},
266291
catch: (e) => (e instanceof Error ? e : new Error(String(e))),
@@ -743,7 +768,7 @@ export namespace MCP {
743768

744769
return yield* Effect.tryPromise({
745770
try: () => {
746-
const client = new Client({ name: "opencode", version: Installation.VERSION })
771+
const client = new Client({ name: "opencode", version: Installation.VERSION }, { jsonSchemaValidator: tolerantValidator })
747772
return client.connect(transport).then(() => ({ authorizationUrl: "", oauthState }))
748773
},
749774
catch: (error) => error,

0 commit comments

Comments
 (0)