-
Notifications
You must be signed in to change notification settings - Fork 1.3k
feat: zod v4 #869
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: zod v4 #869
Changes from 8 commits
6db0e68
08513d3
3b28aac
1cc8c7b
9b178b7
9298723
aca892b
e2d351c
2010203
8531d8e
1461fa2
9cf8b2e
5a761b5
30fb023
3cd0546
5a64ed1
ab26df8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,4 @@ | ||
import { | ||
ZodTypeAny, | ||
ZodTypeDef, | ||
ZodType, | ||
ParseInput, | ||
ParseReturnType, | ||
RawCreateParams, | ||
ZodErrorMap, | ||
ProcessedCreateParams, | ||
} from "zod"; | ||
import { ZodTypeAny } from "zod/v4"; | ||
|
||
export enum McpZodTypeKind { | ||
Completable = "McpCompletable", | ||
|
@@ -17,82 +8,38 @@ export type CompleteCallback<T extends ZodTypeAny = ZodTypeAny> = ( | |
value: T["_input"], | ||
context?: { | ||
arguments?: Record<string, string>; | ||
}, | ||
} | ||
) => T["_input"][] | Promise<T["_input"][]>; | ||
|
||
export interface CompletableDef<T extends ZodTypeAny = ZodTypeAny> | ||
extends ZodTypeDef { | ||
export interface CompletableDef<T extends ZodTypeAny = ZodTypeAny> { | ||
type: T; | ||
complete: CompleteCallback<T>; | ||
typeName: McpZodTypeKind.Completable; | ||
} | ||
|
||
export class Completable<T extends ZodTypeAny> extends ZodType< | ||
T["_output"], | ||
CompletableDef<T>, | ||
T["_input"] | ||
> { | ||
_parse(input: ParseInput): ParseReturnType<this["_output"]> { | ||
const { ctx } = this._processInputParams(input); | ||
const data = ctx.data; | ||
return this._def.type._parse({ | ||
data, | ||
path: ctx.path, | ||
parent: ctx, | ||
}); | ||
} | ||
|
||
unwrap() { | ||
return this._def.type; | ||
} | ||
|
||
static create = <T extends ZodTypeAny>( | ||
type: T, | ||
params: RawCreateParams & { | ||
complete: CompleteCallback<T>; | ||
}, | ||
): Completable<T> => { | ||
return new Completable({ | ||
type, | ||
typeName: McpZodTypeKind.Completable, | ||
complete: params.complete, | ||
...processCreateParams(params), | ||
}); | ||
}; | ||
} | ||
|
||
/** | ||
* Wraps a Zod type to provide autocompletion capabilities. Useful for, e.g., prompt arguments in MCP. | ||
*/ | ||
export function completable<T extends ZodTypeAny>( | ||
schema: T, | ||
complete: CompleteCallback<T>, | ||
): Completable<T> { | ||
return Completable.create(schema, { ...schema._def, complete }); | ||
} | ||
|
||
// Not sure why this isn't exported from Zod: | ||
// https://github.com/colinhacks/zod/blob/f7ad26147ba291cb3fb257545972a8e00e767470/src/types.ts#L130 | ||
function processCreateParams(params: RawCreateParams): ProcessedCreateParams { | ||
if (!params) return {}; | ||
const { errorMap, invalid_type_error, required_error, description } = params; | ||
if (errorMap && (invalid_type_error || required_error)) { | ||
throw new Error( | ||
`Can't use "invalid_type_error" or "required_error" in conjunction with custom error map.`, | ||
); | ||
complete: CompleteCallback<T> | ||
): T & { | ||
_def: (T extends { _def: infer D } ? D : unknown) & CompletableDef<T>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. first of all I want to claim that I am not a part of the mcp organization so I hope I am not a blocking phase for this feature. I really am anticipating for the SDK to support zod v4 and I want to thank you for your time and effort! I took a little more time to further learn about zod and the SDK so I can provide some meaningful insights if possible according to https://zod.dev/library-authors?id=how-to-support-zod-3-and-zod-4-simultaneously you can support both z3 and z4 which can make this feature (in case that the maintainers of this repo are willing) a non-breaking change so it can be part of sdk v1 rather than v2. I suggest asking the maintainers before re-implementing the whole PR of course! besides backward compatibility, it looks like one thing that I am not sure about is your choice to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also am not sure about the |
||
} { | ||
const target = schema as unknown as { _def?: Record<string, unknown> }; | ||
const originalDef = (target._def ?? {}) as Record<string, unknown>; | ||
// Only mutate the existing _def object to respect read-only property semantics | ||
if ( | ||
(originalDef as { typeName?: unknown }).typeName !== | ||
McpZodTypeKind.Completable | ||
) { | ||
(originalDef as { typeName?: McpZodTypeKind; type?: ZodTypeAny }).typeName = | ||
McpZodTypeKind.Completable; | ||
(originalDef as { typeName?: McpZodTypeKind; type?: ZodTypeAny }).type = | ||
schema; | ||
} | ||
if (errorMap) return { errorMap: errorMap, description }; | ||
const customMap: ZodErrorMap = (iss, ctx) => { | ||
const { message } = params; | ||
|
||
if (iss.code === "invalid_enum_value") { | ||
return { message: message ?? ctx.defaultError }; | ||
} | ||
if (typeof ctx.data === "undefined") { | ||
return { message: message ?? required_error ?? ctx.defaultError }; | ||
} | ||
if (iss.code !== "invalid_type") return { message: ctx.defaultError }; | ||
return { message: message ?? invalid_type_error ?? ctx.defaultError }; | ||
(originalDef as { complete?: CompleteCallback<T> }).complete = complete; | ||
return schema as unknown as T & { | ||
_def: (T extends { _def: infer D } ? D : unknown) & CompletableDef<T>; | ||
}; | ||
return { errorMap: customMap, description }; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is
prefault
preferred in this case?as far as I understand from https://zod.dev/v4/changelog#default-updates it's about expected input type (in their example it's a string) and output type (in their example the string goes through transform to output a number)
looks like here default will work as well since input type is same as output type - number
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah we can use default! let me get it updated. the util I used defaults to
prefault
to preserve default functionality so I left it as is.