Skip to content

Commit 6669217

Browse files
committed
chore: use zod v4 methods for types and auth
1 parent 205d379 commit 6669217

File tree

4 files changed

+189
-175
lines changed

4 files changed

+189
-175
lines changed

src/client/v3/index.v3.test.ts

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
/* eslint-disable @typescript-eslint/no-unused-expressions */
44
import { Client } from '../index.js';
55
import * as z from 'zod/v3';
6+
import { AnyObjectSchema } from '../../server/zod-compat.js';
67
import {
78
RequestSchema,
89
NotificationSchema,
@@ -601,39 +602,48 @@ test('should allow setRequestHandler for declared elicitation capability', () =>
601602
* Test that custom request/notification/result schemas can be used with the Client class.
602603
*/
603604
test('should typecheck', () => {
604-
const GetWeatherRequestSchema = RequestSchema.extend({
605+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
606+
const GetWeatherRequestSchema = (RequestSchema as unknown as z.ZodObject<any>).extend({
605607
method: z.literal('weather/get'),
606608
params: z.object({
607609
city: z.string()
608610
})
609-
});
611+
}) as AnyObjectSchema;
610612

611-
const GetForecastRequestSchema = RequestSchema.extend({
613+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
614+
const GetForecastRequestSchema = (RequestSchema as unknown as z.ZodObject<any>).extend({
612615
method: z.literal('weather/forecast'),
613616
params: z.object({
614617
city: z.string(),
615618
days: z.number()
616619
})
617-
});
620+
}) as AnyObjectSchema;
618621

619-
const WeatherForecastNotificationSchema = NotificationSchema.extend({
622+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
623+
const WeatherForecastNotificationSchema = (NotificationSchema as unknown as z.ZodObject<any>).extend({
620624
method: z.literal('weather/alert'),
621625
params: z.object({
622626
severity: z.enum(['warning', 'watch']),
623627
message: z.string()
624628
})
625-
});
626-
627-
const WeatherRequestSchema = GetWeatherRequestSchema.or(GetForecastRequestSchema);
628-
const WeatherNotificationSchema = WeatherForecastNotificationSchema;
629-
const WeatherResultSchema = ResultSchema.extend({
629+
}) as AnyObjectSchema;
630+
631+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
632+
const WeatherRequestSchema = (GetWeatherRequestSchema as unknown as z.ZodObject<any>).or(
633+
GetForecastRequestSchema as unknown as z.ZodObject<any>
634+
) as AnyObjectSchema;
635+
const WeatherNotificationSchema = WeatherForecastNotificationSchema as AnyObjectSchema;
636+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
637+
const WeatherResultSchema = (ResultSchema as unknown as z.ZodObject<any>).extend({
630638
temperature: z.number(),
631639
conditions: z.string()
632-
});
640+
}) as AnyObjectSchema;
633641

634-
type WeatherRequest = z.infer<typeof WeatherRequestSchema>;
635-
type WeatherNotification = z.infer<typeof WeatherNotificationSchema>;
636-
type WeatherResult = z.infer<typeof WeatherResultSchema>;
642+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
643+
type InferSchema<T> = T extends z.ZodType<infer Output, any, any> ? Output : never;
644+
type WeatherRequest = InferSchema<typeof WeatherRequestSchema>;
645+
type WeatherNotification = InferSchema<typeof WeatherNotificationSchema>;
646+
type WeatherResult = InferSchema<typeof WeatherResultSchema>;
637647

638648
// Create a typed Client for weather data
639649
const weatherClient = new Client<WeatherRequest, WeatherNotification, WeatherResult>(

src/server/v3/index.v3.test.ts

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
SUPPORTED_PROTOCOL_VERSIONS
2020
} from '../../types.js';
2121
import { Server } from '../index.js';
22+
import { AnyObjectSchema } from '../zod-compat.js';
2223

2324
test('should accept latest protocol version', async () => {
2425
let sendPromiseResolve: (value: unknown) => void;
@@ -632,39 +633,48 @@ test('should only allow setRequestHandler for declared capabilities', () => {
632633
Test that custom request/notification/result schemas can be used with the Server class.
633634
*/
634635
test('should typecheck', () => {
635-
const GetWeatherRequestSchema = RequestSchema.extend({
636+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
637+
const GetWeatherRequestSchema = (RequestSchema as unknown as z.ZodObject<any>).extend({
636638
method: z.literal('weather/get'),
637639
params: z.object({
638640
city: z.string()
639641
})
640-
});
642+
}) as AnyObjectSchema;
641643

642-
const GetForecastRequestSchema = RequestSchema.extend({
644+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
645+
const GetForecastRequestSchema = (RequestSchema as unknown as z.ZodObject<any>).extend({
643646
method: z.literal('weather/forecast'),
644647
params: z.object({
645648
city: z.string(),
646649
days: z.number()
647650
})
648-
});
651+
}) as AnyObjectSchema;
649652

650-
const WeatherForecastNotificationSchema = NotificationSchema.extend({
653+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
654+
const WeatherForecastNotificationSchema = (NotificationSchema as unknown as z.ZodObject<any>).extend({
651655
method: z.literal('weather/alert'),
652656
params: z.object({
653657
severity: z.enum(['warning', 'watch']),
654658
message: z.string()
655659
})
656-
});
657-
658-
const WeatherRequestSchema = GetWeatherRequestSchema.or(GetForecastRequestSchema);
659-
const WeatherNotificationSchema = WeatherForecastNotificationSchema;
660-
const WeatherResultSchema = ResultSchema.extend({
660+
}) as AnyObjectSchema;
661+
662+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
663+
const WeatherRequestSchema = (GetWeatherRequestSchema as unknown as z.ZodObject<any>).or(
664+
GetForecastRequestSchema as unknown as z.ZodObject<any>
665+
) as AnyObjectSchema;
666+
const WeatherNotificationSchema = WeatherForecastNotificationSchema as AnyObjectSchema;
667+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
668+
const WeatherResultSchema = (ResultSchema as unknown as z.ZodObject<any>).extend({
661669
temperature: z.number(),
662670
conditions: z.string()
663-
});
671+
}) as AnyObjectSchema;
664672

665-
type WeatherRequest = z.infer<typeof WeatherRequestSchema>;
666-
type WeatherNotification = z.infer<typeof WeatherNotificationSchema>;
667-
type WeatherResult = z.infer<typeof WeatherResultSchema>;
673+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
674+
type InferSchema<T> = T extends z.ZodType<infer Output, any, any> ? Output : never;
675+
type WeatherRequest = InferSchema<typeof WeatherRequestSchema>;
676+
type WeatherNotification = InferSchema<typeof WeatherNotificationSchema>;
677+
type WeatherResult = InferSchema<typeof WeatherResultSchema>;
668678

669679
// Create a typed Server for weather data
670680
const weatherServer = new Server<WeatherRequest, WeatherNotification, WeatherResult>(
@@ -691,7 +701,9 @@ test('should typecheck', () => {
691701
});
692702

693703
weatherServer.setNotificationHandler(WeatherForecastNotificationSchema, notification => {
694-
console.log(`Weather alert: ${notification.params.message}`);
704+
// Type assertion needed for v3/v4 schema mixing
705+
const params = notification.params as { message: string; severity: 'warning' | 'watch' };
706+
console.log(`Weather alert: ${params.message}`);
695707
});
696708
});
697709

src/shared/auth.ts

Lines changed: 78 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -28,105 +28,100 @@ export const SafeUrlSchema = z
2828
/**
2929
* RFC 9728 OAuth Protected Resource Metadata
3030
*/
31-
export const OAuthProtectedResourceMetadataSchema = z
32-
.object({
33-
resource: z.string().url(),
34-
authorization_servers: z.array(SafeUrlSchema).optional(),
35-
jwks_uri: z.string().url().optional(),
36-
scopes_supported: z.array(z.string()).optional(),
37-
bearer_methods_supported: z.array(z.string()).optional(),
38-
resource_signing_alg_values_supported: z.array(z.string()).optional(),
39-
resource_name: z.string().optional(),
40-
resource_documentation: z.string().optional(),
41-
resource_policy_uri: z.string().url().optional(),
42-
resource_tos_uri: z.string().url().optional(),
43-
tls_client_certificate_bound_access_tokens: z.boolean().optional(),
44-
authorization_details_types_supported: z.array(z.string()).optional(),
45-
dpop_signing_alg_values_supported: z.array(z.string()).optional(),
46-
dpop_bound_access_tokens_required: z.boolean().optional()
47-
})
48-
.passthrough();
31+
export const OAuthProtectedResourceMetadataSchema = z.looseObject({
32+
resource: z.string().url(),
33+
authorization_servers: z.array(SafeUrlSchema).optional(),
34+
jwks_uri: z.string().url().optional(),
35+
scopes_supported: z.array(z.string()).optional(),
36+
bearer_methods_supported: z.array(z.string()).optional(),
37+
resource_signing_alg_values_supported: z.array(z.string()).optional(),
38+
resource_name: z.string().optional(),
39+
resource_documentation: z.string().optional(),
40+
resource_policy_uri: z.string().url().optional(),
41+
resource_tos_uri: z.string().url().optional(),
42+
tls_client_certificate_bound_access_tokens: z.boolean().optional(),
43+
authorization_details_types_supported: z.array(z.string()).optional(),
44+
dpop_signing_alg_values_supported: z.array(z.string()).optional(),
45+
dpop_bound_access_tokens_required: z.boolean().optional()
46+
});
4947

5048
/**
5149
* RFC 8414 OAuth 2.0 Authorization Server Metadata
5250
*/
53-
export const OAuthMetadataSchema = z
54-
.object({
55-
issuer: z.string(),
56-
authorization_endpoint: SafeUrlSchema,
57-
token_endpoint: SafeUrlSchema,
58-
registration_endpoint: SafeUrlSchema.optional(),
59-
scopes_supported: z.array(z.string()).optional(),
60-
response_types_supported: z.array(z.string()),
61-
response_modes_supported: z.array(z.string()).optional(),
62-
grant_types_supported: z.array(z.string()).optional(),
63-
token_endpoint_auth_methods_supported: z.array(z.string()).optional(),
64-
token_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(),
65-
service_documentation: SafeUrlSchema.optional(),
66-
revocation_endpoint: SafeUrlSchema.optional(),
67-
revocation_endpoint_auth_methods_supported: z.array(z.string()).optional(),
68-
revocation_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(),
69-
introspection_endpoint: z.string().optional(),
70-
introspection_endpoint_auth_methods_supported: z.array(z.string()).optional(),
71-
introspection_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(),
72-
code_challenge_methods_supported: z.array(z.string()).optional()
73-
})
74-
.passthrough();
51+
export const OAuthMetadataSchema = z.looseObject({
52+
issuer: z.string(),
53+
authorization_endpoint: SafeUrlSchema,
54+
token_endpoint: SafeUrlSchema,
55+
registration_endpoint: SafeUrlSchema.optional(),
56+
scopes_supported: z.array(z.string()).optional(),
57+
response_types_supported: z.array(z.string()),
58+
response_modes_supported: z.array(z.string()).optional(),
59+
grant_types_supported: z.array(z.string()).optional(),
60+
token_endpoint_auth_methods_supported: z.array(z.string()).optional(),
61+
token_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(),
62+
service_documentation: SafeUrlSchema.optional(),
63+
revocation_endpoint: SafeUrlSchema.optional(),
64+
revocation_endpoint_auth_methods_supported: z.array(z.string()).optional(),
65+
revocation_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(),
66+
introspection_endpoint: z.string().optional(),
67+
introspection_endpoint_auth_methods_supported: z.array(z.string()).optional(),
68+
introspection_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(),
69+
code_challenge_methods_supported: z.array(z.string()).optional()
70+
});
7571

7672
/**
7773
* OpenID Connect Discovery 1.0 Provider Metadata
7874
* see: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
7975
*/
80-
export const OpenIdProviderMetadataSchema = z
81-
.object({
82-
issuer: z.string(),
83-
authorization_endpoint: SafeUrlSchema,
84-
token_endpoint: SafeUrlSchema,
85-
userinfo_endpoint: SafeUrlSchema.optional(),
86-
jwks_uri: SafeUrlSchema,
87-
registration_endpoint: SafeUrlSchema.optional(),
88-
scopes_supported: z.array(z.string()).optional(),
89-
response_types_supported: z.array(z.string()),
90-
response_modes_supported: z.array(z.string()).optional(),
91-
grant_types_supported: z.array(z.string()).optional(),
92-
acr_values_supported: z.array(z.string()).optional(),
93-
subject_types_supported: z.array(z.string()),
94-
id_token_signing_alg_values_supported: z.array(z.string()),
95-
id_token_encryption_alg_values_supported: z.array(z.string()).optional(),
96-
id_token_encryption_enc_values_supported: z.array(z.string()).optional(),
97-
userinfo_signing_alg_values_supported: z.array(z.string()).optional(),
98-
userinfo_encryption_alg_values_supported: z.array(z.string()).optional(),
99-
userinfo_encryption_enc_values_supported: z.array(z.string()).optional(),
100-
request_object_signing_alg_values_supported: z.array(z.string()).optional(),
101-
request_object_encryption_alg_values_supported: z.array(z.string()).optional(),
102-
request_object_encryption_enc_values_supported: z.array(z.string()).optional(),
103-
token_endpoint_auth_methods_supported: z.array(z.string()).optional(),
104-
token_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(),
105-
display_values_supported: z.array(z.string()).optional(),
106-
claim_types_supported: z.array(z.string()).optional(),
107-
claims_supported: z.array(z.string()).optional(),
108-
service_documentation: z.string().optional(),
109-
claims_locales_supported: z.array(z.string()).optional(),
110-
ui_locales_supported: z.array(z.string()).optional(),
111-
claims_parameter_supported: z.boolean().optional(),
112-
request_parameter_supported: z.boolean().optional(),
113-
request_uri_parameter_supported: z.boolean().optional(),
114-
require_request_uri_registration: z.boolean().optional(),
115-
op_policy_uri: SafeUrlSchema.optional(),
116-
op_tos_uri: SafeUrlSchema.optional()
117-
})
118-
.passthrough();
76+
export const OpenIdProviderMetadataSchema = z.looseObject({
77+
issuer: z.string(),
78+
authorization_endpoint: SafeUrlSchema,
79+
token_endpoint: SafeUrlSchema,
80+
userinfo_endpoint: SafeUrlSchema.optional(),
81+
jwks_uri: SafeUrlSchema,
82+
registration_endpoint: SafeUrlSchema.optional(),
83+
scopes_supported: z.array(z.string()).optional(),
84+
response_types_supported: z.array(z.string()),
85+
response_modes_supported: z.array(z.string()).optional(),
86+
grant_types_supported: z.array(z.string()).optional(),
87+
acr_values_supported: z.array(z.string()).optional(),
88+
subject_types_supported: z.array(z.string()),
89+
id_token_signing_alg_values_supported: z.array(z.string()),
90+
id_token_encryption_alg_values_supported: z.array(z.string()).optional(),
91+
id_token_encryption_enc_values_supported: z.array(z.string()).optional(),
92+
userinfo_signing_alg_values_supported: z.array(z.string()).optional(),
93+
userinfo_encryption_alg_values_supported: z.array(z.string()).optional(),
94+
userinfo_encryption_enc_values_supported: z.array(z.string()).optional(),
95+
request_object_signing_alg_values_supported: z.array(z.string()).optional(),
96+
request_object_encryption_alg_values_supported: z.array(z.string()).optional(),
97+
request_object_encryption_enc_values_supported: z.array(z.string()).optional(),
98+
token_endpoint_auth_methods_supported: z.array(z.string()).optional(),
99+
token_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(),
100+
display_values_supported: z.array(z.string()).optional(),
101+
claim_types_supported: z.array(z.string()).optional(),
102+
claims_supported: z.array(z.string()).optional(),
103+
service_documentation: z.string().optional(),
104+
claims_locales_supported: z.array(z.string()).optional(),
105+
ui_locales_supported: z.array(z.string()).optional(),
106+
claims_parameter_supported: z.boolean().optional(),
107+
request_parameter_supported: z.boolean().optional(),
108+
request_uri_parameter_supported: z.boolean().optional(),
109+
require_request_uri_registration: z.boolean().optional(),
110+
op_policy_uri: SafeUrlSchema.optional(),
111+
op_tos_uri: SafeUrlSchema.optional()
112+
});
119113

120114
/**
121115
* OpenID Connect Discovery metadata that may include OAuth 2.0 fields
122116
* This schema represents the real-world scenario where OIDC providers
123117
* return a mix of OpenID Connect and OAuth 2.0 metadata fields
124118
*/
125-
export const OpenIdProviderDiscoveryMetadataSchema = OpenIdProviderMetadataSchema.merge(
126-
OAuthMetadataSchema.pick({
119+
export const OpenIdProviderDiscoveryMetadataSchema = z.object({
120+
...OpenIdProviderMetadataSchema.shape,
121+
...OAuthMetadataSchema.pick({
127122
code_challenge_methods_supported: true
128-
})
129-
);
123+
}).shape
124+
});
130125

131126
/**
132127
* OAuth 2.1 token response

0 commit comments

Comments
 (0)