@@ -6,10 +6,10 @@ import type {
66 ClientContext ,
77 ClientNotification ,
88 ClientRequest ,
9- ClientResult ,
109 CompleteRequest ,
1110 GetPromptRequest ,
1211 Implementation ,
12+ JSONRPCRequest ,
1313 JsonSchemaType ,
1414 JsonSchemaValidator ,
1515 jsonSchemaValidator ,
@@ -26,8 +26,7 @@ import type {
2626 ReadResourceRequest ,
2727 RequestMethod ,
2828 RequestOptions ,
29- RequestTypeMap ,
30- ResultTypeMap ,
29+ Result ,
3130 ServerCapabilities ,
3231 SubscribeRequest ,
3332 TaskManagerOptions ,
@@ -200,6 +199,28 @@ export type ClientOptions = ProtocolOptions & {
200199 *
201200 * The client will automatically begin the initialization flow with the server when {@linkcode connect} is called.
202201 *
202+ * To handle server-initiated requests (sampling, elicitation, roots), call {@linkcode setRequestHandler}.
203+ * The client must declare the corresponding capability for the handler to be accepted. For
204+ * `sampling/createMessage` and `elicitation/create`, the handler is automatically wrapped with
205+ * schema validation for both the incoming request and the returned result.
206+ *
207+ * @example Handling a sampling request
208+ * ```ts source="./client.examples.ts#Client_setRequestHandler_sampling"
209+ * client.setRequestHandler('sampling/createMessage', async request => {
210+ * const lastMessage = request.params.messages.at(-1);
211+ * console.log('Sampling request:', lastMessage);
212+ *
213+ * // In production, send messages to your LLM here
214+ * return {
215+ * model: 'my-model',
216+ * role: 'assistant' as const,
217+ * content: {
218+ * type: 'text' as const,
219+ * text: 'Response from the model'
220+ * }
221+ * };
222+ * });
223+ * ```
203224 */
204225export class Client extends Protocol < ClientContext > {
205226 private _serverCapabilities ?: ServerCapabilities ;
@@ -308,37 +329,15 @@ export class Client extends Protocol<ClientContext> {
308329 }
309330
310331 /**
311- * Registers a handler for server-initiated requests (sampling, elicitation, roots).
312- * The client must declare the corresponding capability for the handler to be accepted.
313- * Replaces any previously registered handler for the same method.
314- *
315- * For `sampling/createMessage` and `elicitation/create`, the handler is automatically
316- * wrapped with schema validation for both the incoming request and the returned result.
317- *
318- * @example Handling a sampling request
319- * ```ts source="./client.examples.ts#Client_setRequestHandler_sampling"
320- * client.setRequestHandler('sampling/createMessage', async request => {
321- * const lastMessage = request.params.messages.at(-1);
322- * console.log('Sampling request:', lastMessage);
323- *
324- * // In production, send messages to your LLM here
325- * return {
326- * model: 'my-model',
327- * role: 'assistant' as const,
328- * content: {
329- * type: 'text' as const,
330- * text: 'Response from the model'
331- * }
332- * };
333- * });
334- * ```
332+ * Enforces client-side validation for `elicitation/create` and `sampling/createMessage`
333+ * regardless of how the handler was registered.
335334 */
336- public override setRequestHandler < M extends RequestMethod > (
337- method : M ,
338- handler : ( request : RequestTypeMap [ M ] , ctx : ClientContext ) => ResultTypeMap [ M ] | Promise < ResultTypeMap [ M ] >
339- ) : void {
335+ protected override _wrapHandler (
336+ method : string ,
337+ handler : ( request : JSONRPCRequest , ctx : ClientContext ) => Promise < Result >
338+ ) : ( request : JSONRPCRequest , ctx : ClientContext ) => Promise < Result > {
340339 if ( method === 'elicitation/create' ) {
341- const wrappedHandler = async ( request : RequestTypeMap [ M ] , ctx : ClientContext ) : Promise < ClientResult > => {
340+ return async ( request , ctx ) => {
342341 const validatedRequest = parseSchema ( ElicitRequestSchema , request ) ;
343342 if ( ! validatedRequest . success ) {
344343 // Type guard: if success is false, error is guaranteed to exist
@@ -359,7 +358,7 @@ export class Client extends Protocol<ClientContext> {
359358 throw new ProtocolError ( ProtocolErrorCode . InvalidParams , 'Client does not support URL-mode elicitation requests' ) ;
360359 }
361360
362- const result = await Promise . resolve ( handler ( request , ctx ) ) ;
361+ const result = await handler ( request , ctx ) ;
363362
364363 // When task creation is requested, validate and return CreateTaskResult
365364 if ( params . task ) {
@@ -402,13 +401,10 @@ export class Client extends Protocol<ClientContext> {
402401
403402 return validatedResult ;
404403 } ;
405-
406- // Install the wrapped handler
407- return super . setRequestHandler ( method , wrappedHandler ) ;
408404 }
409405
410406 if ( method === 'sampling/createMessage' ) {
411- const wrappedHandler = async ( request : RequestTypeMap [ M ] , ctx : ClientContext ) : Promise < ClientResult > => {
407+ return async ( request , ctx ) => {
412408 const validatedRequest = parseSchema ( CreateMessageRequestSchema , request ) ;
413409 if ( ! validatedRequest . success ) {
414410 const errorMessage =
@@ -418,7 +414,7 @@ export class Client extends Protocol<ClientContext> {
418414
419415 const { params } = validatedRequest . data ;
420416
421- const result = await Promise . resolve ( handler ( request , ctx ) ) ;
417+ const result = await handler ( request , ctx ) ;
422418
423419 // When task creation is requested, validate and return CreateTaskResult
424420 if ( params . task ) {
@@ -445,13 +441,9 @@ export class Client extends Protocol<ClientContext> {
445441
446442 return validationResult . data ;
447443 } ;
448-
449- // Install the wrapped handler
450- return super . setRequestHandler ( method , wrappedHandler ) ;
451444 }
452445
453- // Other handlers use default behavior
454- return super . setRequestHandler ( method , handler ) ;
446+ return handler ;
455447 }
456448
457449 protected assertCapability ( capability : keyof ServerCapabilities , method : string ) : void {
0 commit comments