@@ -24,6 +24,7 @@ import {
2424} from '../../types.js' ;
2525import { getDisplayName } from '../../shared/metadataUtils.js' ;
2626import { Ajv } from 'ajv' ;
27+ import { InMemoryTaskStore } from '../../experimental/tasks/stores/in-memory.js' ;
2728
2829// Create readline interface for user input
2930const readline = createInterface ( {
@@ -65,6 +66,7 @@ function printHelp(): void {
6566 console . log ( ' greet [name] - Call the greet tool' ) ;
6667 console . log ( ' multi-greet [name] - Call the multi-greet tool with notifications' ) ;
6768 console . log ( ' collect-info [type] - Test form elicitation with collect-user-info tool (contact/preferences/feedback)' ) ;
69+ console . log ( ' collect-info-task [type] - Test bidirectional task support (server+client tasks) with elicitation' ) ;
6870 console . log ( ' start-notifications [interval] [count] - Start periodic notifications' ) ;
6971 console . log ( ' run-notifications-tool-with-resumability [interval] [count] - Run notification tool with resumability' ) ;
7072 console . log ( ' list-prompts - List available prompts' ) ;
@@ -131,6 +133,10 @@ function commandLoop(): void {
131133 await callCollectInfoTool ( args [ 1 ] || 'contact' ) ;
132134 break ;
133135
136+ case 'collect-info-task' :
137+ await callCollectInfoWithTask ( args [ 1 ] || 'contact' ) ;
138+ break ;
139+
134140 case 'start-notifications' : {
135141 const interval = args [ 1 ] ? parseInt ( args [ 1 ] , 10 ) : 2000 ;
136142 const count = args [ 2 ] ? parseInt ( args [ 2 ] , 10 ) : 10 ;
@@ -232,7 +238,10 @@ async function connect(url?: string): Promise<void> {
232238 console . log ( `Connecting to ${ serverUrl } ...` ) ;
233239
234240 try {
235- // Create a new client with form elicitation capability
241+ // Create task store for client-side task support
242+ const clientTaskStore = new InMemoryTaskStore ( ) ;
243+
244+ // Create a new client with form elicitation capability and task support
236245 client = new Client (
237246 {
238247 name : 'example-client' ,
@@ -242,25 +251,46 @@ async function connect(url?: string): Promise<void> {
242251 capabilities : {
243252 elicitation : {
244253 form : { }
254+ } ,
255+ tasks : {
256+ requests : {
257+ elicitation : {
258+ create : { }
259+ }
260+ }
245261 }
246- }
262+ } ,
263+ taskStore : clientTaskStore
247264 }
248265 ) ;
249266 client . onerror = error => {
250267 console . error ( '\x1b[31mClient error:' , error , '\x1b[0m' ) ;
251268 } ;
252269
253- // Set up elicitation request handler with proper validation
254- client . setRequestHandler ( ElicitRequestSchema , async request => {
270+ // Set up elicitation request handler with proper validation and task support
271+ client . setRequestHandler ( ElicitRequestSchema , async ( request , extra ) => {
255272 if ( request . params . mode !== 'form' ) {
256273 throw new McpError ( ErrorCode . InvalidParams , `Unsupported elicitation mode: ${ request . params . mode } ` ) ;
257274 }
258275 console . log ( '\n🔔 Elicitation (form) Request Received:' ) ;
259276 console . log ( `Message: ${ request . params . message } ` ) ;
260277 console . log ( `Related Task: ${ request . params . _meta ?. [ RELATED_TASK_META_KEY ] ?. taskId } ` ) ;
278+ console . log ( `Task Creation Requested: ${ request . params . task ? 'yes' : 'no' } ` ) ;
261279 console . log ( 'Requested Schema:' ) ;
262280 console . log ( JSON . stringify ( request . params . requestedSchema , null , 2 ) ) ;
263281
282+ // Helper to return result, optionally creating a task if requested
283+ const returnResult = async ( result : { action : 'accept' | 'decline' | 'cancel' ; content ?: Record < string , unknown > } ) => {
284+ if ( request . params . task && extra . taskStore ) {
285+ // Create a task and store the result
286+ const task = await extra . taskStore . createTask ( { ttl : extra . taskRequestedTtl } ) ;
287+ await extra . taskStore . storeTaskResult ( task . taskId , 'completed' , result ) ;
288+ console . log ( `📋 Created client-side task: ${ task . taskId } ` ) ;
289+ return { task } ;
290+ }
291+ return result ;
292+ } ;
293+
264294 const schema = request . params . requestedSchema ;
265295 const properties = schema . properties ;
266296 const required = schema . required || [ ] ;
@@ -381,7 +411,7 @@ async function connect(url?: string): Promise<void> {
381411 }
382412
383413 if ( inputCancelled ) {
384- return { action : 'cancel' } ;
414+ return returnResult ( { action : 'cancel' } ) ;
385415 }
386416
387417 // If we didn't complete all fields due to an error, try again
@@ -394,7 +424,7 @@ async function connect(url?: string): Promise<void> {
394424 continue ;
395425 } else {
396426 console . log ( 'Maximum attempts reached. Declining request.' ) ;
397- return { action : 'decline' } ;
427+ return returnResult ( { action : 'decline' } ) ;
398428 }
399429 }
400430
@@ -412,7 +442,7 @@ async function connect(url?: string): Promise<void> {
412442 continue ;
413443 } else {
414444 console . log ( 'Maximum attempts reached. Declining request.' ) ;
415- return { action : 'decline' } ;
445+ return returnResult ( { action : 'decline' } ) ;
416446 }
417447 }
418448
@@ -427,24 +457,24 @@ async function connect(url?: string): Promise<void> {
427457 } ) ;
428458
429459 if ( confirmAnswer === 'yes' || confirmAnswer === 'y' ) {
430- return {
460+ return returnResult ( {
431461 action : 'accept' ,
432462 content
433- } ;
463+ } ) ;
434464 } else if ( confirmAnswer === 'cancel' || confirmAnswer === 'c' ) {
435- return { action : 'cancel' } ;
465+ return returnResult ( { action : 'cancel' } ) ;
436466 } else if ( confirmAnswer === 'no' || confirmAnswer === 'n' ) {
437467 if ( attempts < maxAttempts ) {
438468 console . log ( 'Please re-enter the information...' ) ;
439469 continue ;
440470 } else {
441- return { action : 'decline' } ;
471+ return returnResult ( { action : 'decline' } ) ;
442472 }
443473 }
444474 }
445475
446476 console . log ( 'Maximum attempts reached. Declining request.' ) ;
447- return { action : 'decline' } ;
477+ return returnResult ( { action : 'decline' } ) ;
448478 } ) ;
449479
450480 transport = new StreamableHTTPClientTransport ( new URL ( serverUrl ) , {
@@ -641,6 +671,12 @@ async function callCollectInfoTool(infoType: string): Promise<void> {
641671 await callTool ( 'collect-user-info' , { infoType } ) ;
642672}
643673
674+ async function callCollectInfoWithTask ( infoType : string ) : Promise < void > {
675+ console . log ( `\n🔄 Testing bidirectional task support with collect-user-info-task tool (${ infoType } )...` ) ;
676+ console . log ( 'This will create a task on the server, which will elicit input and create a task on the client.\n' ) ;
677+ await callToolTask ( 'collect-user-info-task' , { infoType } ) ;
678+ }
679+
644680async function startNotifications ( interval : number , count : number ) : Promise < void > {
645681 console . log ( `Starting notification stream: interval=${ interval } ms, count=${ count || 'unlimited' } ` ) ;
646682 await callTool ( 'start-notification-stream' , { interval, count } ) ;
0 commit comments