diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 459f76961a..258500d7bd 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -178,7 +178,7 @@ export const Info = z .record( z.string(), z.union([ - ConfigMCP.Info, + ConfigMCP.Info.zod, z .object({ enabled: z.boolean(), diff --git a/packages/opencode/src/config/mcp.ts b/packages/opencode/src/config/mcp.ts index 5036cd6e4f..8b77bc4c28 100644 --- a/packages/opencode/src/config/mcp.ts +++ b/packages/opencode/src/config/mcp.ts @@ -1,68 +1,62 @@ -import z from "zod" +import { Schema } from "effect" +import { zod } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" -export const Local = z - .object({ - type: z.literal("local").describe("Type of MCP server connection"), - command: z.string().array().describe("Command and arguments to run the MCP server"), - environment: z - .record(z.string(), z.string()) - .optional() - .describe("Environment variables to set when running the MCP server"), - enabled: z.boolean().optional().describe("Enable or disable the MCP server on startup"), - timeout: z - .number() - .int() - .positive() - .optional() - .describe("Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified."), - }) - .strict() - .meta({ - ref: "McpLocalConfig", - }) +export class Local extends Schema.Class("McpLocalConfig")({ + type: Schema.Literal("local").annotate({ description: "Type of MCP server connection" }), + command: Schema.mutable(Schema.Array(Schema.String)).annotate({ + description: "Command and arguments to run the MCP server", + }), + environment: Schema.optional(Schema.Record(Schema.String, Schema.String)).annotate({ + description: "Environment variables to set when running the MCP server", + }), + enabled: Schema.optional(Schema.Boolean).annotate({ + description: "Enable or disable the MCP server on startup", + }), + timeout: Schema.optional(Schema.Number).annotate({ + description: "Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified.", + }), +}) { + static readonly zod = zod(this) +} -export const OAuth = z - .object({ - clientId: z - .string() - .optional() - .describe("OAuth client ID. If not provided, dynamic client registration (RFC 7591) will be attempted."), - clientSecret: z.string().optional().describe("OAuth client secret (if required by the authorization server)"), - scope: z.string().optional().describe("OAuth scopes to request during authorization"), - redirectUri: z - .string() - .optional() - .describe("OAuth redirect URI (default: http://127.0.0.1:19876/mcp/oauth/callback)."), - }) - .strict() - .meta({ - ref: "McpOAuthConfig", - }) -export type OAuth = z.infer +export class OAuth extends Schema.Class("McpOAuthConfig")({ + clientId: Schema.optional(Schema.String).annotate({ + description: "OAuth client ID. If not provided, dynamic client registration (RFC 7591) will be attempted.", + }), + clientSecret: Schema.optional(Schema.String).annotate({ + description: "OAuth client secret (if required by the authorization server)", + }), + scope: Schema.optional(Schema.String).annotate({ description: "OAuth scopes to request during authorization" }), + redirectUri: Schema.optional(Schema.String).annotate({ + description: "OAuth redirect URI (default: http://127.0.0.1:19876/mcp/oauth/callback).", + }), +}) { + static readonly zod = zod(this) +} -export const Remote = z - .object({ - type: z.literal("remote").describe("Type of MCP server connection"), - url: z.string().describe("URL of the remote MCP server"), - enabled: z.boolean().optional().describe("Enable or disable the MCP server on startup"), - headers: z.record(z.string(), z.string()).optional().describe("Headers to send with the request"), - oauth: z - .union([OAuth, z.literal(false)]) - .optional() - .describe("OAuth authentication configuration for the MCP server. Set to false to disable OAuth auto-detection."), - timeout: z - .number() - .int() - .positive() - .optional() - .describe("Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified."), - }) - .strict() - .meta({ - ref: "McpRemoteConfig", - }) +export class Remote extends Schema.Class("McpRemoteConfig")({ + type: Schema.Literal("remote").annotate({ description: "Type of MCP server connection" }), + url: Schema.String.annotate({ description: "URL of the remote MCP server" }), + enabled: Schema.optional(Schema.Boolean).annotate({ + description: "Enable or disable the MCP server on startup", + }), + headers: Schema.optional(Schema.Record(Schema.String, Schema.String)).annotate({ + description: "Headers to send with the request", + }), + oauth: Schema.optional(Schema.Union([OAuth, Schema.Literal(false)])).annotate({ + description: "OAuth authentication configuration for the MCP server. Set to false to disable OAuth auto-detection.", + }), + timeout: Schema.optional(Schema.Number).annotate({ + description: "Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified.", + }), +}) { + static readonly zod = zod(this) +} -export const Info = z.discriminatedUnion("type", [Local, Remote]) -export type Info = z.infer +export const Info = Schema.Union([Local, Remote]) + .annotate({ discriminator: "type" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Info = Schema.Schema.Type export * as ConfigMCP from "./mcp" diff --git a/packages/opencode/src/server/routes/instance/mcp.ts b/packages/opencode/src/server/routes/instance/mcp.ts index 197185bde0..b42cfb5314 100644 --- a/packages/opencode/src/server/routes/instance/mcp.ts +++ b/packages/opencode/src/server/routes/instance/mcp.ts @@ -54,7 +54,7 @@ export const McpRoutes = lazy(() => "json", z.object({ name: z.string(), - config: ConfigMCP.Info, + config: ConfigMCP.Info.zod, }), ), async (c) => {