1+ // Run with: npx tsx src/examples/client/elicitationUrlExample.ts
2+ //
3+ // This example demonstrates how to use URL elicitation to securely
4+ // collect user input in a remote (HTTP) server.
5+ // URL elicitation allows servers to prompt the end-user to open a URL in their browser
6+ // to collect sensitive information.
7+
18import { Client } from '../../client/index.js' ;
29import { StreamableHTTPClientTransport } from '../../client/streamableHttp.js' ;
310import { createInterface } from 'node:readline' ;
@@ -489,7 +496,8 @@ async function connect(url?: string): Promise<void> {
489496 {
490497 capabilities : {
491498 elicitation : {
492- form : { } ,
499+ // Only URL elicitation is supported in this demo
500+ // (see server/elicitationExample.ts for a demo of form mode elicitation)
493501 url : { }
494502 }
495503 }
@@ -499,13 +507,34 @@ async function connect(url?: string): Promise<void> {
499507 // Only create a new transport if one doesn't exist
500508 transport = new StreamableHTTPClientTransport ( new URL ( serverUrl ) , {
501509 sessionId : sessionId ,
502- authProvider : oauthProvider
510+ authProvider : oauthProvider ,
511+ requestInit : {
512+ headers : {
513+ 'Content-Type' : 'application/json' ,
514+ Accept : 'application/json, text/event-stream'
515+ }
516+ }
503517 } ) ;
504518 }
505519
506520 // Set up elicitation request handler with proper validation
507521 client . setRequestHandler ( ElicitRequestSchema , elicitationRequestHandler ) ;
508522
523+ // Set up notification handler for elicitation completion
524+ client . setNotificationHandler ( ElicitationCompleteNotificationSchema , notification => {
525+ const { elicitationId } = notification . params ;
526+ const pending = pendingURLElicitations . get ( elicitationId ) ;
527+ if ( pending ) {
528+ clearTimeout ( pending . timeout ) ;
529+ pendingURLElicitations . delete ( elicitationId ) ;
530+ console . log ( `\x1b[32m✅ Elicitation ${ elicitationId } completed!\x1b[0m` ) ;
531+ pending . resolve ( ) ;
532+ } else {
533+ // Shouldn't happen - discard it!
534+ console . warn ( `Received completion notification for unknown elicitation: ${ elicitationId } ` ) ;
535+ }
536+ } ) ;
537+
509538 try {
510539 console . log ( `Connecting to ${ serverUrl } ...` ) ;
511540 // Connect the client
@@ -521,40 +550,6 @@ async function connect(url?: string): Promise<void> {
521550 await transport . finishAuth ( authCode ) ;
522551 console . log ( '🔐 Authorization code received:' , authCode ) ;
523552 console . log ( '🔌 Reconnecting with authenticated transport...' ) ;
524- transport = new StreamableHTTPClientTransport ( new URL ( serverUrl ) , {
525- sessionId : sessionId ,
526- authProvider : oauthProvider
527- } ) ;
528- await client . connect ( transport ) ;
529- } else {
530- console . error ( 'Failed to connect:' , error ) ;
531- client = null ;
532- transport = null ;
533- return ;
534- }
535-
536- if ( url ) {
537- serverUrl = url ;
538- }
539-
540- // Create a new client with elicitation capability
541- client = new Client (
542- {
543- name : 'example-client' ,
544- version : '1.0.0'
545- } ,
546- {
547- capabilities : {
548- elicitation : {
549- // Only URL elicitation is supported in this demo
550- // (see server/elicitationExample.ts for a demo of form mode elicitation)
551- url : { }
552- }
553- }
554- }
555- ) ;
556- if ( ! transport ) {
557- // Only create a new transport if one doesn't exist
558553 transport = new StreamableHTTPClientTransport ( new URL ( serverUrl ) , {
559554 sessionId : sessionId ,
560555 authProvider : oauthProvider ,
@@ -565,64 +560,18 @@ async function connect(url?: string): Promise<void> {
565560 }
566561 }
567562 } ) ;
568- }
569-
570- // Set up elicitation request handler with proper validation
571- client . setRequestHandler ( ElicitRequestSchema , elicitationRequestHandler ) ;
572-
573- // Set up notification handler for elicitation completion
574- client . setNotificationHandler ( ElicitationCompleteNotificationSchema , notification => {
575- const { elicitationId } = notification . params ;
576- const pending = pendingURLElicitations . get ( elicitationId ) ;
577- if ( pending ) {
578- clearTimeout ( pending . timeout ) ;
579- pendingURLElicitations . delete ( elicitationId ) ;
580- console . log ( `\x1b[32m✅ Elicitation ${ elicitationId } completed!\x1b[0m` ) ;
581- pending . resolve ( ) ;
582- } else {
583- // Shouldn't happen - discard it!
584- console . warn ( `Received completion notification for unknown elicitation: ${ elicitationId } ` ) ;
585- }
586- } ) ;
587-
588- try {
589- console . log ( `Connecting to ${ serverUrl } ...` ) ;
590- // Connect the client
591563 await client . connect ( transport ) ;
592- sessionId = transport . sessionId ;
593- console . log ( 'Transport created with session ID:' , sessionId ) ;
594- console . log ( 'Connected to MCP server' ) ;
595- } catch ( error ) {
596- if ( error instanceof UnauthorizedError ) {
597- console . log ( 'OAuth required - waiting for authorization...' ) ;
598- const callbackPromise = waitForOAuthCallback ( ) ;
599- const authCode = await callbackPromise ;
600- await transport . finishAuth ( authCode ) ;
601- console . log ( '🔐 Authorization code received:' , authCode ) ;
602- console . log ( '🔌 Reconnecting with authenticated transport...' ) ;
603- transport = new StreamableHTTPClientTransport ( new URL ( serverUrl ) , {
604- sessionId : sessionId ,
605- authProvider : oauthProvider ,
606- requestInit : {
607- headers : {
608- 'Content-Type' : 'application/json' ,
609- Accept : 'application/json, text/event-stream'
610- }
611- }
612- } ) ;
613- await client . connect ( transport ) ;
614- } else {
615- console . error ( 'Failed to connect:' , error ) ;
616- client = null ;
617- transport = null ;
618- return ;
619- }
564+ } else {
565+ console . error ( 'Failed to connect:' , error ) ;
566+ client = null ;
567+ transport = null ;
568+ return ;
620569 }
621- // Set up error handler after connection is established so we don't double log errors
622- client . onerror = error => {
623- console . error ( '\x1b[31mClient error:' , error , '\x1b[0m' ) ;
624- } ;
625570 }
571+ // Set up error handler after connection is established so we don't double log errors
572+ client . onerror = error => {
573+ console . error ( '\x1b[31mClient error:' , error , '\x1b[0m' ) ;
574+ } ;
626575}
627576
628577async function disconnect ( ) : Promise < void > {
0 commit comments