Add extracted source directory and README navigation

This commit is contained in:
Shawn Bot
2026-03-31 14:56:06 +00:00
parent 6252bb6eb5
commit 91e01d755b
4757 changed files with 984951 additions and 0 deletions

View File

@@ -0,0 +1,180 @@
/**
* Base class for all OAuth errors
*/
export class OAuthError extends Error {
constructor(message, errorUri) {
super(message);
this.errorUri = errorUri;
this.name = this.constructor.name;
}
/**
* Converts the error to a standard OAuth error response object
*/
toResponseObject() {
const response = {
error: this.errorCode,
error_description: this.message
};
if (this.errorUri) {
response.error_uri = this.errorUri;
}
return response;
}
get errorCode() {
return this.constructor.errorCode;
}
}
/**
* Invalid request error - The request is missing a required parameter,
* includes an invalid parameter value, includes a parameter more than once,
* or is otherwise malformed.
*/
export class InvalidRequestError extends OAuthError {
}
InvalidRequestError.errorCode = 'invalid_request';
/**
* Invalid client error - Client authentication failed (e.g., unknown client, no client
* authentication included, or unsupported authentication method).
*/
export class InvalidClientError extends OAuthError {
}
InvalidClientError.errorCode = 'invalid_client';
/**
* Invalid grant error - The provided authorization grant or refresh token is
* invalid, expired, revoked, does not match the redirection URI used in the
* authorization request, or was issued to another client.
*/
export class InvalidGrantError extends OAuthError {
}
InvalidGrantError.errorCode = 'invalid_grant';
/**
* Unauthorized client error - The authenticated client is not authorized to use
* this authorization grant type.
*/
export class UnauthorizedClientError extends OAuthError {
}
UnauthorizedClientError.errorCode = 'unauthorized_client';
/**
* Unsupported grant type error - The authorization grant type is not supported
* by the authorization server.
*/
export class UnsupportedGrantTypeError extends OAuthError {
}
UnsupportedGrantTypeError.errorCode = 'unsupported_grant_type';
/**
* Invalid scope error - The requested scope is invalid, unknown, malformed, or
* exceeds the scope granted by the resource owner.
*/
export class InvalidScopeError extends OAuthError {
}
InvalidScopeError.errorCode = 'invalid_scope';
/**
* Access denied error - The resource owner or authorization server denied the request.
*/
export class AccessDeniedError extends OAuthError {
}
AccessDeniedError.errorCode = 'access_denied';
/**
* Server error - The authorization server encountered an unexpected condition
* that prevented it from fulfilling the request.
*/
export class ServerError extends OAuthError {
}
ServerError.errorCode = 'server_error';
/**
* Temporarily unavailable error - The authorization server is currently unable to
* handle the request due to a temporary overloading or maintenance of the server.
*/
export class TemporarilyUnavailableError extends OAuthError {
}
TemporarilyUnavailableError.errorCode = 'temporarily_unavailable';
/**
* Unsupported response type error - The authorization server does not support
* obtaining an authorization code using this method.
*/
export class UnsupportedResponseTypeError extends OAuthError {
}
UnsupportedResponseTypeError.errorCode = 'unsupported_response_type';
/**
* Unsupported token type error - The authorization server does not support
* the requested token type.
*/
export class UnsupportedTokenTypeError extends OAuthError {
}
UnsupportedTokenTypeError.errorCode = 'unsupported_token_type';
/**
* Invalid token error - The access token provided is expired, revoked, malformed,
* or invalid for other reasons.
*/
export class InvalidTokenError extends OAuthError {
}
InvalidTokenError.errorCode = 'invalid_token';
/**
* Method not allowed error - The HTTP method used is not allowed for this endpoint.
* (Custom, non-standard error)
*/
export class MethodNotAllowedError extends OAuthError {
}
MethodNotAllowedError.errorCode = 'method_not_allowed';
/**
* Too many requests error - Rate limit exceeded.
* (Custom, non-standard error based on RFC 6585)
*/
export class TooManyRequestsError extends OAuthError {
}
TooManyRequestsError.errorCode = 'too_many_requests';
/**
* Invalid client metadata error - The client metadata is invalid.
* (Custom error for dynamic client registration - RFC 7591)
*/
export class InvalidClientMetadataError extends OAuthError {
}
InvalidClientMetadataError.errorCode = 'invalid_client_metadata';
/**
* Insufficient scope error - The request requires higher privileges than provided by the access token.
*/
export class InsufficientScopeError extends OAuthError {
}
InsufficientScopeError.errorCode = 'insufficient_scope';
/**
* Invalid target error - The requested resource is invalid, missing, unknown, or malformed.
* (Custom error for resource indicators - RFC 8707)
*/
export class InvalidTargetError extends OAuthError {
}
InvalidTargetError.errorCode = 'invalid_target';
/**
* A utility class for defining one-off error codes
*/
export class CustomOAuthError extends OAuthError {
constructor(customErrorCode, message, errorUri) {
super(message, errorUri);
this.customErrorCode = customErrorCode;
}
get errorCode() {
return this.customErrorCode;
}
}
/**
* A full list of all OAuthErrors, enabling parsing from error responses
*/
export const OAUTH_ERRORS = {
[InvalidRequestError.errorCode]: InvalidRequestError,
[InvalidClientError.errorCode]: InvalidClientError,
[InvalidGrantError.errorCode]: InvalidGrantError,
[UnauthorizedClientError.errorCode]: UnauthorizedClientError,
[UnsupportedGrantTypeError.errorCode]: UnsupportedGrantTypeError,
[InvalidScopeError.errorCode]: InvalidScopeError,
[AccessDeniedError.errorCode]: AccessDeniedError,
[ServerError.errorCode]: ServerError,
[TemporarilyUnavailableError.errorCode]: TemporarilyUnavailableError,
[UnsupportedResponseTypeError.errorCode]: UnsupportedResponseTypeError,
[UnsupportedTokenTypeError.errorCode]: UnsupportedTokenTypeError,
[InvalidTokenError.errorCode]: InvalidTokenError,
[MethodNotAllowedError.errorCode]: MethodNotAllowedError,
[TooManyRequestsError.errorCode]: TooManyRequestsError,
[InvalidClientMetadataError.errorCode]: InvalidClientMetadataError,
[InsufficientScopeError.errorCode]: InsufficientScopeError,
[InvalidTargetError.errorCode]: InvalidTargetError
};
//# sourceMappingURL=errors.js.map

View File

@@ -0,0 +1,440 @@
import { mergeCapabilities, Protocol } from '../shared/protocol.js';
import { CreateMessageResultSchema, CreateMessageResultWithToolsSchema, ElicitResultSchema, EmptyResultSchema, ErrorCode, InitializedNotificationSchema, InitializeRequestSchema, LATEST_PROTOCOL_VERSION, ListRootsResultSchema, LoggingLevelSchema, McpError, SetLevelRequestSchema, SUPPORTED_PROTOCOL_VERSIONS, CallToolRequestSchema, CallToolResultSchema, CreateTaskResultSchema } from '../types.js';
import { AjvJsonSchemaValidator } from '../validation/ajv-provider.js';
import { getObjectShape, isZ4Schema, safeParse } from './zod-compat.js';
import { ExperimentalServerTasks } from '../experimental/tasks/server.js';
import { assertToolsCallTaskCapability, assertClientRequestTaskCapability } from '../experimental/tasks/helpers.js';
/**
* An MCP server on top of a pluggable transport.
*
* This server will automatically respond to the initialization flow as initiated from the client.
*
* To use with custom types, extend the base Request/Notification/Result types and pass them as type parameters:
*
* ```typescript
* // Custom schemas
* const CustomRequestSchema = RequestSchema.extend({...})
* const CustomNotificationSchema = NotificationSchema.extend({...})
* const CustomResultSchema = ResultSchema.extend({...})
*
* // Type aliases
* type CustomRequest = z.infer<typeof CustomRequestSchema>
* type CustomNotification = z.infer<typeof CustomNotificationSchema>
* type CustomResult = z.infer<typeof CustomResultSchema>
*
* // Create typed server
* const server = new Server<CustomRequest, CustomNotification, CustomResult>({
* name: "CustomServer",
* version: "1.0.0"
* })
* ```
* @deprecated Use `McpServer` instead for the high-level API. Only use `Server` for advanced use cases.
*/
export class Server extends Protocol {
/**
* Initializes this server with the given name and version information.
*/
constructor(_serverInfo, options) {
super(options);
this._serverInfo = _serverInfo;
// Map log levels by session id
this._loggingLevels = new Map();
// Map LogLevelSchema to severity index
this.LOG_LEVEL_SEVERITY = new Map(LoggingLevelSchema.options.map((level, index) => [level, index]));
// Is a message with the given level ignored in the log level set for the given session id?
this.isMessageIgnored = (level, sessionId) => {
const currentLevel = this._loggingLevels.get(sessionId);
return currentLevel ? this.LOG_LEVEL_SEVERITY.get(level) < this.LOG_LEVEL_SEVERITY.get(currentLevel) : false;
};
this._capabilities = options?.capabilities ?? {};
this._instructions = options?.instructions;
this._jsonSchemaValidator = options?.jsonSchemaValidator ?? new AjvJsonSchemaValidator();
this.setRequestHandler(InitializeRequestSchema, request => this._oninitialize(request));
this.setNotificationHandler(InitializedNotificationSchema, () => this.oninitialized?.());
if (this._capabilities.logging) {
this.setRequestHandler(SetLevelRequestSchema, async (request, extra) => {
const transportSessionId = extra.sessionId || extra.requestInfo?.headers['mcp-session-id'] || undefined;
const { level } = request.params;
const parseResult = LoggingLevelSchema.safeParse(level);
if (parseResult.success) {
this._loggingLevels.set(transportSessionId, parseResult.data);
}
return {};
});
}
}
/**
* Access experimental features.
*
* WARNING: These APIs are experimental and may change without notice.
*
* @experimental
*/
get experimental() {
if (!this._experimental) {
this._experimental = {
tasks: new ExperimentalServerTasks(this)
};
}
return this._experimental;
}
/**
* Registers new capabilities. This can only be called before connecting to a transport.
*
* The new capabilities will be merged with any existing capabilities previously given (e.g., at initialization).
*/
registerCapabilities(capabilities) {
if (this.transport) {
throw new Error('Cannot register capabilities after connecting to transport');
}
this._capabilities = mergeCapabilities(this._capabilities, capabilities);
}
/**
* Override request handler registration to enforce server-side validation for tools/call.
*/
setRequestHandler(requestSchema, handler) {
const shape = getObjectShape(requestSchema);
const methodSchema = shape?.method;
if (!methodSchema) {
throw new Error('Schema is missing a method literal');
}
// Extract literal value using type-safe property access
let methodValue;
if (isZ4Schema(methodSchema)) {
const v4Schema = methodSchema;
const v4Def = v4Schema._zod?.def;
methodValue = v4Def?.value ?? v4Schema.value;
}
else {
const v3Schema = methodSchema;
const legacyDef = v3Schema._def;
methodValue = legacyDef?.value ?? v3Schema.value;
}
if (typeof methodValue !== 'string') {
throw new Error('Schema method literal must be a string');
}
const method = methodValue;
if (method === 'tools/call') {
const wrappedHandler = async (request, extra) => {
const validatedRequest = safeParse(CallToolRequestSchema, request);
if (!validatedRequest.success) {
const errorMessage = validatedRequest.error instanceof Error ? validatedRequest.error.message : String(validatedRequest.error);
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call request: ${errorMessage}`);
}
const { params } = validatedRequest.data;
const result = await Promise.resolve(handler(request, extra));
// When task creation is requested, validate and return CreateTaskResult
if (params.task) {
const taskValidationResult = safeParse(CreateTaskResultSchema, result);
if (!taskValidationResult.success) {
const errorMessage = taskValidationResult.error instanceof Error
? taskValidationResult.error.message
: String(taskValidationResult.error);
throw new McpError(ErrorCode.InvalidParams, `Invalid task creation result: ${errorMessage}`);
}
return taskValidationResult.data;
}
// For non-task requests, validate against CallToolResultSchema
const validationResult = safeParse(CallToolResultSchema, result);
if (!validationResult.success) {
const errorMessage = validationResult.error instanceof Error ? validationResult.error.message : String(validationResult.error);
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call result: ${errorMessage}`);
}
return validationResult.data;
};
// Install the wrapped handler
return super.setRequestHandler(requestSchema, wrappedHandler);
}
// Other handlers use default behavior
return super.setRequestHandler(requestSchema, handler);
}
assertCapabilityForMethod(method) {
switch (method) {
case 'sampling/createMessage':
if (!this._clientCapabilities?.sampling) {
throw new Error(`Client does not support sampling (required for ${method})`);
}
break;
case 'elicitation/create':
if (!this._clientCapabilities?.elicitation) {
throw new Error(`Client does not support elicitation (required for ${method})`);
}
break;
case 'roots/list':
if (!this._clientCapabilities?.roots) {
throw new Error(`Client does not support listing roots (required for ${method})`);
}
break;
case 'ping':
// No specific capability required for ping
break;
}
}
assertNotificationCapability(method) {
switch (method) {
case 'notifications/message':
if (!this._capabilities.logging) {
throw new Error(`Server does not support logging (required for ${method})`);
}
break;
case 'notifications/resources/updated':
case 'notifications/resources/list_changed':
if (!this._capabilities.resources) {
throw new Error(`Server does not support notifying about resources (required for ${method})`);
}
break;
case 'notifications/tools/list_changed':
if (!this._capabilities.tools) {
throw new Error(`Server does not support notifying of tool list changes (required for ${method})`);
}
break;
case 'notifications/prompts/list_changed':
if (!this._capabilities.prompts) {
throw new Error(`Server does not support notifying of prompt list changes (required for ${method})`);
}
break;
case 'notifications/elicitation/complete':
if (!this._clientCapabilities?.elicitation?.url) {
throw new Error(`Client does not support URL elicitation (required for ${method})`);
}
break;
case 'notifications/cancelled':
// Cancellation notifications are always allowed
break;
case 'notifications/progress':
// Progress notifications are always allowed
break;
}
}
assertRequestHandlerCapability(method) {
// Task handlers are registered in Protocol constructor before _capabilities is initialized
// Skip capability check for task methods during initialization
if (!this._capabilities) {
return;
}
switch (method) {
case 'completion/complete':
if (!this._capabilities.completions) {
throw new Error(`Server does not support completions (required for ${method})`);
}
break;
case 'logging/setLevel':
if (!this._capabilities.logging) {
throw new Error(`Server does not support logging (required for ${method})`);
}
break;
case 'prompts/get':
case 'prompts/list':
if (!this._capabilities.prompts) {
throw new Error(`Server does not support prompts (required for ${method})`);
}
break;
case 'resources/list':
case 'resources/templates/list':
case 'resources/read':
if (!this._capabilities.resources) {
throw new Error(`Server does not support resources (required for ${method})`);
}
break;
case 'tools/call':
case 'tools/list':
if (!this._capabilities.tools) {
throw new Error(`Server does not support tools (required for ${method})`);
}
break;
case 'tasks/get':
case 'tasks/list':
case 'tasks/result':
case 'tasks/cancel':
if (!this._capabilities.tasks) {
throw new Error(`Server does not support tasks capability (required for ${method})`);
}
break;
case 'ping':
case 'initialize':
// No specific capability required for these methods
break;
}
}
assertTaskCapability(method) {
assertClientRequestTaskCapability(this._clientCapabilities?.tasks?.requests, method, 'Client');
}
assertTaskHandlerCapability(method) {
// Task handlers are registered in Protocol constructor before _capabilities is initialized
// Skip capability check for task methods during initialization
if (!this._capabilities) {
return;
}
assertToolsCallTaskCapability(this._capabilities.tasks?.requests, method, 'Server');
}
async _oninitialize(request) {
const requestedVersion = request.params.protocolVersion;
this._clientCapabilities = request.params.capabilities;
this._clientVersion = request.params.clientInfo;
const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.includes(requestedVersion) ? requestedVersion : LATEST_PROTOCOL_VERSION;
return {
protocolVersion,
capabilities: this.getCapabilities(),
serverInfo: this._serverInfo,
...(this._instructions && { instructions: this._instructions })
};
}
/**
* After initialization has completed, this will be populated with the client's reported capabilities.
*/
getClientCapabilities() {
return this._clientCapabilities;
}
/**
* After initialization has completed, this will be populated with information about the client's name and version.
*/
getClientVersion() {
return this._clientVersion;
}
getCapabilities() {
return this._capabilities;
}
async ping() {
return this.request({ method: 'ping' }, EmptyResultSchema);
}
// Implementation
async createMessage(params, options) {
// Capability check - only required when tools/toolChoice are provided
if (params.tools || params.toolChoice) {
if (!this._clientCapabilities?.sampling?.tools) {
throw new Error('Client does not support sampling tools capability.');
}
}
// Message structure validation - always validate tool_use/tool_result pairs.
// These may appear even without tools/toolChoice in the current request when
// a previous sampling request returned tool_use and this is a follow-up with results.
if (params.messages.length > 0) {
const lastMessage = params.messages[params.messages.length - 1];
const lastContent = Array.isArray(lastMessage.content) ? lastMessage.content : [lastMessage.content];
const hasToolResults = lastContent.some(c => c.type === 'tool_result');
const previousMessage = params.messages.length > 1 ? params.messages[params.messages.length - 2] : undefined;
const previousContent = previousMessage
? Array.isArray(previousMessage.content)
? previousMessage.content
: [previousMessage.content]
: [];
const hasPreviousToolUse = previousContent.some(c => c.type === 'tool_use');
if (hasToolResults) {
if (lastContent.some(c => c.type !== 'tool_result')) {
throw new Error('The last message must contain only tool_result content if any is present');
}
if (!hasPreviousToolUse) {
throw new Error('tool_result blocks are not matching any tool_use from the previous message');
}
}
if (hasPreviousToolUse) {
const toolUseIds = new Set(previousContent.filter(c => c.type === 'tool_use').map(c => c.id));
const toolResultIds = new Set(lastContent.filter(c => c.type === 'tool_result').map(c => c.toolUseId));
if (toolUseIds.size !== toolResultIds.size || ![...toolUseIds].every(id => toolResultIds.has(id))) {
throw new Error('ids of tool_result blocks and tool_use blocks from previous message do not match');
}
}
}
// Use different schemas based on whether tools are provided
if (params.tools) {
return this.request({ method: 'sampling/createMessage', params }, CreateMessageResultWithToolsSchema, options);
}
return this.request({ method: 'sampling/createMessage', params }, CreateMessageResultSchema, options);
}
/**
* Creates an elicitation request for the given parameters.
* For backwards compatibility, `mode` may be omitted for form requests and will default to `'form'`.
* @param params The parameters for the elicitation request.
* @param options Optional request options.
* @returns The result of the elicitation request.
*/
async elicitInput(params, options) {
const mode = (params.mode ?? 'form');
switch (mode) {
case 'url': {
if (!this._clientCapabilities?.elicitation?.url) {
throw new Error('Client does not support url elicitation.');
}
const urlParams = params;
return this.request({ method: 'elicitation/create', params: urlParams }, ElicitResultSchema, options);
}
case 'form': {
if (!this._clientCapabilities?.elicitation?.form) {
throw new Error('Client does not support form elicitation.');
}
const formParams = params.mode === 'form' ? params : { ...params, mode: 'form' };
const result = await this.request({ method: 'elicitation/create', params: formParams }, ElicitResultSchema, options);
if (result.action === 'accept' && result.content && formParams.requestedSchema) {
try {
const validator = this._jsonSchemaValidator.getValidator(formParams.requestedSchema);
const validationResult = validator(result.content);
if (!validationResult.valid) {
throw new McpError(ErrorCode.InvalidParams, `Elicitation response content does not match requested schema: ${validationResult.errorMessage}`);
}
}
catch (error) {
if (error instanceof McpError) {
throw error;
}
throw new McpError(ErrorCode.InternalError, `Error validating elicitation response: ${error instanceof Error ? error.message : String(error)}`);
}
}
return result;
}
}
}
/**
* Creates a reusable callback that, when invoked, will send a `notifications/elicitation/complete`
* notification for the specified elicitation ID.
*
* @param elicitationId The ID of the elicitation to mark as complete.
* @param options Optional notification options. Useful when the completion notification should be related to a prior request.
* @returns A function that emits the completion notification when awaited.
*/
createElicitationCompletionNotifier(elicitationId, options) {
if (!this._clientCapabilities?.elicitation?.url) {
throw new Error('Client does not support URL elicitation (required for notifications/elicitation/complete)');
}
return () => this.notification({
method: 'notifications/elicitation/complete',
params: {
elicitationId
}
}, options);
}
async listRoots(params, options) {
return this.request({ method: 'roots/list', params }, ListRootsResultSchema, options);
}
/**
* Sends a logging message to the client, if connected.
* Note: You only need to send the parameters object, not the entire JSON RPC message
* @see LoggingMessageNotification
* @param params
* @param sessionId optional for stateless and backward compatibility
*/
async sendLoggingMessage(params, sessionId) {
if (this._capabilities.logging) {
if (!this.isMessageIgnored(params.level, sessionId)) {
return this.notification({ method: 'notifications/message', params });
}
}
}
async sendResourceUpdated(params) {
return this.notification({
method: 'notifications/resources/updated',
params
});
}
async sendResourceListChanged() {
return this.notification({
method: 'notifications/resources/list_changed'
});
}
async sendToolListChanged() {
return this.notification({ method: 'notifications/tools/list_changed' });
}
async sendPromptListChanged() {
return this.notification({ method: 'notifications/prompts/list_changed' });
}
}
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1,75 @@
import process from 'node:process';
import { ReadBuffer, serializeMessage } from '../shared/stdio.js';
/**
* Server transport for stdio: this communicates with an MCP client by reading from the current process' stdin and writing to stdout.
*
* This transport is only available in Node.js environments.
*/
export class StdioServerTransport {
constructor(_stdin = process.stdin, _stdout = process.stdout) {
this._stdin = _stdin;
this._stdout = _stdout;
this._readBuffer = new ReadBuffer();
this._started = false;
// Arrow functions to bind `this` properly, while maintaining function identity.
this._ondata = (chunk) => {
this._readBuffer.append(chunk);
this.processReadBuffer();
};
this._onerror = (error) => {
this.onerror?.(error);
};
}
/**
* Starts listening for messages on stdin.
*/
async start() {
if (this._started) {
throw new Error('StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.');
}
this._started = true;
this._stdin.on('data', this._ondata);
this._stdin.on('error', this._onerror);
}
processReadBuffer() {
while (true) {
try {
const message = this._readBuffer.readMessage();
if (message === null) {
break;
}
this.onmessage?.(message);
}
catch (error) {
this.onerror?.(error);
}
}
}
async close() {
// Remove our event listeners first
this._stdin.off('data', this._ondata);
this._stdin.off('error', this._onerror);
// Check if we were the only data listener
const remainingDataListeners = this._stdin.listenerCount('data');
if (remainingDataListeners === 0) {
// Only pause stdin if we were the only listener
// This prevents interfering with other parts of the application that might be using stdin
this._stdin.pause();
}
// Clear the buffer and notify closure
this._readBuffer.clear();
this.onclose?.();
}
send(message) {
return new Promise(resolve => {
const json = serializeMessage(message);
if (this._stdout.write(json)) {
resolve();
}
else {
this._stdout.once('drain', resolve);
}
});
}
}
//# sourceMappingURL=stdio.js.map

View File

@@ -0,0 +1,209 @@
// zod-compat.ts
// ----------------------------------------------------
// Unified types + helpers to accept Zod v3 and v4 (Mini)
// ----------------------------------------------------
import * as z3rt from 'zod/v3';
import * as z4mini from 'zod/v4-mini';
// --- Runtime detection ---
export function isZ4Schema(s) {
// Present on Zod 4 (Classic & Mini) schemas; absent on Zod 3
const schema = s;
return !!schema._zod;
}
// --- Schema construction ---
export function objectFromShape(shape) {
const values = Object.values(shape);
if (values.length === 0)
return z4mini.object({}); // default to v4 Mini
const allV4 = values.every(isZ4Schema);
const allV3 = values.every(s => !isZ4Schema(s));
if (allV4)
return z4mini.object(shape);
if (allV3)
return z3rt.object(shape);
throw new Error('Mixed Zod versions detected in object shape.');
}
// --- Unified parsing ---
export function safeParse(schema, data) {
if (isZ4Schema(schema)) {
// Mini exposes top-level safeParse
const result = z4mini.safeParse(schema, data);
return result;
}
const v3Schema = schema;
const result = v3Schema.safeParse(data);
return result;
}
export async function safeParseAsync(schema, data) {
if (isZ4Schema(schema)) {
// Mini exposes top-level safeParseAsync
const result = await z4mini.safeParseAsync(schema, data);
return result;
}
const v3Schema = schema;
const result = await v3Schema.safeParseAsync(data);
return result;
}
// --- Shape extraction ---
export function getObjectShape(schema) {
if (!schema)
return undefined;
// Zod v3 exposes `.shape`; Zod v4 keeps the shape on `_zod.def.shape`
let rawShape;
if (isZ4Schema(schema)) {
const v4Schema = schema;
rawShape = v4Schema._zod?.def?.shape;
}
else {
const v3Schema = schema;
rawShape = v3Schema.shape;
}
if (!rawShape)
return undefined;
if (typeof rawShape === 'function') {
try {
return rawShape();
}
catch {
return undefined;
}
}
return rawShape;
}
// --- Schema normalization ---
/**
* Normalizes a schema to an object schema. Handles both:
* - Already-constructed object schemas (v3 or v4)
* - Raw shapes that need to be wrapped into object schemas
*/
export function normalizeObjectSchema(schema) {
if (!schema)
return undefined;
// First check if it's a raw shape (Record<string, AnySchema>)
// Raw shapes don't have _def or _zod properties and aren't schemas themselves
if (typeof schema === 'object') {
// Check if it's actually a ZodRawShapeCompat (not a schema instance)
// by checking if it lacks schema-like internal properties
const asV3 = schema;
const asV4 = schema;
// If it's not a schema instance (no _def or _zod), it might be a raw shape
if (!asV3._def && !asV4._zod) {
// Check if all values are schemas (heuristic to confirm it's a raw shape)
const values = Object.values(schema);
if (values.length > 0 &&
values.every(v => typeof v === 'object' &&
v !== null &&
(v._def !== undefined ||
v._zod !== undefined ||
typeof v.parse === 'function'))) {
return objectFromShape(schema);
}
}
}
// If we get here, it should be an AnySchema (not a raw shape)
// Check if it's already an object schema
if (isZ4Schema(schema)) {
// Check if it's a v4 object
const v4Schema = schema;
const def = v4Schema._zod?.def;
if (def && (def.type === 'object' || def.shape !== undefined)) {
return schema;
}
}
else {
// Check if it's a v3 object
const v3Schema = schema;
if (v3Schema.shape !== undefined) {
return schema;
}
}
return undefined;
}
// --- Error message extraction ---
/**
* Safely extracts an error message from a parse result error.
* Zod errors can have different structures, so we handle various cases.
*/
export function getParseErrorMessage(error) {
if (error && typeof error === 'object') {
// Try common error structures
if ('message' in error && typeof error.message === 'string') {
return error.message;
}
if ('issues' in error && Array.isArray(error.issues) && error.issues.length > 0) {
const firstIssue = error.issues[0];
if (firstIssue && typeof firstIssue === 'object' && 'message' in firstIssue) {
return String(firstIssue.message);
}
}
// Fallback: try to stringify the error
try {
return JSON.stringify(error);
}
catch {
return String(error);
}
}
return String(error);
}
// --- Schema metadata access ---
/**
* Gets the description from a schema, if available.
* Works with both Zod v3 and v4.
*
* Both versions expose a `.description` getter that returns the description
* from their respective internal storage (v3: _def, v4: globalRegistry).
*/
export function getSchemaDescription(schema) {
return schema.description;
}
/**
* Checks if a schema is optional.
* Works with both Zod v3 and v4.
*/
export function isSchemaOptional(schema) {
if (isZ4Schema(schema)) {
const v4Schema = schema;
return v4Schema._zod?.def?.type === 'optional';
}
const v3Schema = schema;
// v3 has isOptional() method
if (typeof schema.isOptional === 'function') {
return schema.isOptional();
}
return v3Schema._def?.typeName === 'ZodOptional';
}
/**
* Gets the literal value from a schema, if it's a literal schema.
* Works with both Zod v3 and v4.
* Returns undefined if the schema is not a literal or the value cannot be determined.
*/
export function getLiteralValue(schema) {
if (isZ4Schema(schema)) {
const v4Schema = schema;
const def = v4Schema._zod?.def;
if (def) {
// Try various ways to get the literal value
if (def.value !== undefined)
return def.value;
if (Array.isArray(def.values) && def.values.length > 0) {
return def.values[0];
}
}
}
const v3Schema = schema;
const def = v3Schema._def;
if (def) {
if (def.value !== undefined)
return def.value;
if (Array.isArray(def.values) && def.values.length > 0) {
return def.values[0];
}
}
// Fallback: check for direct value property (some Zod versions)
const directValue = schema.value;
if (directValue !== undefined)
return directValue;
return undefined;
}
//# sourceMappingURL=zod-compat.js.map

View File

@@ -0,0 +1,51 @@
// zod-json-schema-compat.ts
// ----------------------------------------------------
// JSON Schema conversion for both Zod v3 and Zod v4 (Mini)
// v3 uses your vendored converter; v4 uses Mini's toJSONSchema
// ----------------------------------------------------
import * as z4mini from 'zod/v4-mini';
import { getObjectShape, safeParse, isZ4Schema, getLiteralValue } from './zod-compat.js';
import { zodToJsonSchema } from 'zod-to-json-schema';
function mapMiniTarget(t) {
if (!t)
return 'draft-7';
if (t === 'jsonSchema7' || t === 'draft-7')
return 'draft-7';
if (t === 'jsonSchema2019-09' || t === 'draft-2020-12')
return 'draft-2020-12';
return 'draft-7'; // fallback
}
export function toJsonSchemaCompat(schema, opts) {
if (isZ4Schema(schema)) {
// v4 branch — use Mini's built-in toJSONSchema
return z4mini.toJSONSchema(schema, {
target: mapMiniTarget(opts?.target),
io: opts?.pipeStrategy ?? 'input'
});
}
// v3 branch — use vendored converter
return zodToJsonSchema(schema, {
strictUnions: opts?.strictUnions ?? true,
pipeStrategy: opts?.pipeStrategy ?? 'input'
});
}
export function getMethodLiteral(schema) {
const shape = getObjectShape(schema);
const methodSchema = shape?.method;
if (!methodSchema) {
throw new Error('Schema is missing a method literal');
}
const value = getLiteralValue(methodSchema);
if (typeof value !== 'string') {
throw new Error('Schema method literal must be a string');
}
return value;
}
export function parseWithCompat(schema, data) {
const result = safeParse(schema, data);
if (!result.success) {
throw result.error;
}
return result.data;
}
//# sourceMappingURL=zod-json-schema-compat.js.map