@@ -671,6 +671,174 @@ test('should accept form-mode elicitation request when client advertises empty e
671671 } ) ;
672672} ) ;
673673
674+ test ( 'should reject form-mode elicitation when client only supports URL mode' , async ( ) => {
675+ const client = new Client (
676+ {
677+ name : 'test-client' ,
678+ version : '1.0.0'
679+ } ,
680+ {
681+ capabilities : {
682+ elicitation : {
683+ url : { }
684+ }
685+ }
686+ }
687+ ) ;
688+
689+ const handler = jest . fn ( ) . mockResolvedValue ( {
690+ action : 'cancel'
691+ } ) ;
692+ client . setRequestHandler ( ElicitRequestSchema , handler ) ;
693+
694+ const [ clientTransport , serverTransport ] = InMemoryTransport . createLinkedPair ( ) ;
695+
696+ let resolveResponse : ( ( message : unknown ) => void ) | undefined ;
697+ const responsePromise = new Promise < unknown > ( resolve => {
698+ resolveResponse = resolve ;
699+ } ) ;
700+
701+ serverTransport . onmessage = async message => {
702+ if ( 'method' in message ) {
703+ if ( message . method === 'initialize' ) {
704+ if ( ! ( 'id' in message ) || message . id === undefined ) {
705+ throw new Error ( 'Expected initialize request to include an id' ) ;
706+ }
707+ const messageId = message . id ;
708+ await serverTransport . send ( {
709+ jsonrpc : '2.0' ,
710+ id : messageId ,
711+ result : {
712+ protocolVersion : LATEST_PROTOCOL_VERSION ,
713+ capabilities : { } ,
714+ serverInfo : {
715+ name : 'test-server' ,
716+ version : '1.0.0'
717+ }
718+ }
719+ } ) ;
720+ } else if ( message . method === 'notifications/initialized' ) {
721+ // ignore
722+ }
723+ } else {
724+ resolveResponse ?.( message ) ;
725+ }
726+ } ;
727+
728+ await client . connect ( clientTransport ) ;
729+
730+ // Server shouldn't send this, because the client capabilities
731+ // only advertised URL mode. Test that it's rejected by the client:
732+ const requestId = 1 ;
733+ await serverTransport . send ( {
734+ jsonrpc : '2.0' ,
735+ id : requestId ,
736+ method : 'elicitation/create' ,
737+ params : {
738+ mode : 'form' ,
739+ message : 'Provide your username' ,
740+ requestedSchema : {
741+ type : 'object' ,
742+ properties : {
743+ username : {
744+ type : 'string'
745+ }
746+ }
747+ }
748+ }
749+ } ) ;
750+
751+ const response = ( await responsePromise ) as { id : number ; error : { code : number ; message : string } } ;
752+
753+ expect ( response . id ) . toBe ( requestId ) ;
754+ expect ( response . error . code ) . toBe ( ErrorCode . InvalidParams ) ;
755+ expect ( response . error . message ) . toContain ( 'Client does not support form-mode elicitation requests' ) ;
756+ expect ( handler ) . not . toHaveBeenCalled ( ) ;
757+
758+ await client . close ( ) ;
759+ } ) ;
760+
761+ test ( 'should reject URL-mode elicitation when client only supports form mode' , async ( ) => {
762+ const client = new Client (
763+ {
764+ name : 'test-client' ,
765+ version : '1.0.0'
766+ } ,
767+ {
768+ capabilities : {
769+ elicitation : {
770+ form : { }
771+ }
772+ }
773+ }
774+ ) ;
775+
776+ const handler = jest . fn ( ) . mockResolvedValue ( {
777+ action : 'cancel'
778+ } ) ;
779+ client . setRequestHandler ( ElicitRequestSchema , handler ) ;
780+
781+ const [ clientTransport , serverTransport ] = InMemoryTransport . createLinkedPair ( ) ;
782+
783+ let resolveResponse : ( ( message : unknown ) => void ) | undefined ;
784+ const responsePromise = new Promise < unknown > ( resolve => {
785+ resolveResponse = resolve ;
786+ } ) ;
787+
788+ serverTransport . onmessage = async message => {
789+ if ( 'method' in message ) {
790+ if ( message . method === 'initialize' ) {
791+ if ( ! ( 'id' in message ) || message . id === undefined ) {
792+ throw new Error ( 'Expected initialize request to include an id' ) ;
793+ }
794+ const messageId = message . id ;
795+ await serverTransport . send ( {
796+ jsonrpc : '2.0' ,
797+ id : messageId ,
798+ result : {
799+ protocolVersion : LATEST_PROTOCOL_VERSION ,
800+ capabilities : { } ,
801+ serverInfo : {
802+ name : 'test-server' ,
803+ version : '1.0.0'
804+ }
805+ }
806+ } ) ;
807+ } else if ( message . method === 'notifications/initialized' ) {
808+ // ignore
809+ }
810+ } else {
811+ resolveResponse ?.( message ) ;
812+ }
813+ } ;
814+
815+ await client . connect ( clientTransport ) ;
816+
817+ // Server shouldn't send this, because the client capabilities
818+ // only advertised form mode. Test that it's rejected by the client:
819+ const requestId = 2 ;
820+ await serverTransport . send ( {
821+ jsonrpc : '2.0' ,
822+ id : requestId ,
823+ method : 'elicitation/create' ,
824+ params : {
825+ mode : 'url' ,
826+ message : 'Open the authorization page' ,
827+ elicitationId : 'elicitation-123' ,
828+ url : 'https://example.com/authorize'
829+ }
830+ } ) ;
831+
832+ const response = ( await responsePromise ) as { id : number ; error : { code : number ; message : string } } ;
833+
834+ expect ( response . id ) . toBe ( requestId ) ;
835+ expect ( response . error . code ) . toBe ( ErrorCode . InvalidParams ) ;
836+ expect ( response . error . message ) . toContain ( 'Client does not support URL-mode elicitation requests' ) ;
837+ expect ( handler ) . not . toHaveBeenCalled ( ) ;
838+
839+ await client . close ( ) ;
840+ } ) ;
841+
674842/***
675843 * Test: Type Checking
676844 * Test that custom request/notification/result schemas can be used with the Client class.
0 commit comments