diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 8c92bb6b2e6d..6cd3e6016dd0 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -4,6 +4,8 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js" import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js" import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js" +import { AjvJsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/ajv" +import type { jsonSchemaValidator as JsonSchemaValidatorProvider, JsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/types.js" import { CallToolResultSchema, type Tool as MCPToolDef, @@ -30,6 +32,29 @@ import { makeRuntime } from "@/effect/run-service" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" +/** + * Wraps AjvJsonSchemaValidator to catch schema compilation errors. + * opencode doesn't validate tool output schemas, so compilation failures + * (e.g. from complex or unsupported schemas) should not prevent tool discovery. + */ +class TolerantJsonSchemaValidator implements JsonSchemaValidatorProvider { + private inner = new AjvJsonSchemaValidator() + + getValidator(schema: any): JsonSchemaValidator { + try { + return this.inner.getValidator(schema) + } catch { + return (input: unknown) => ({ + valid: true as const, + data: input as T, + errorMessage: undefined, + }) + } + } +} + +const tolerantValidator = new TolerantJsonSchemaValidator() + export namespace MCP { const log = Log.create({ service: "mcp" }) const DEFAULT_TIMEOUT = 30_000 @@ -260,7 +285,7 @@ export namespace MCP { (t) => Effect.tryPromise({ try: () => { - const client = new Client({ name: "opencode", version: Installation.VERSION }) + const client = new Client({ name: "opencode", version: Installation.VERSION }, { jsonSchemaValidator: tolerantValidator }) return withTimeout(client.connect(t), timeout).then(() => client) }, catch: (e) => (e instanceof Error ? e : new Error(String(e))), @@ -743,7 +768,7 @@ export namespace MCP { return yield* Effect.tryPromise({ try: () => { - const client = new Client({ name: "opencode", version: Installation.VERSION }) + const client = new Client({ name: "opencode", version: Installation.VERSION }, { jsonSchemaValidator: tolerantValidator }) return client.connect(transport).then(() => ({ authorizationUrl: "", oauthState })) }, catch: (error) => error,