Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions packages/opencode/src/mcp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<T>(schema: any): JsonSchemaValidator<T> {
try {
return this.inner.getValidator<T>(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
Expand Down Expand Up @@ -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))),
Expand Down Expand Up @@ -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,
Expand Down
Loading