@@ -22,6 +22,7 @@ import {
2222 ErrorCode ,
2323 McpError
2424} from '../../types.js' ;
25+ import { InMemoryTaskStore } from '../../experimental/tasks/stores/in-memory.js' ;
2526import { getDisplayName } from '../../shared/metadataUtils.js' ;
2627import { Ajv } from 'ajv' ;
2728
@@ -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,11 @@ 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+ }
140+
134141 case 'start-notifications' : {
135142 const interval = args [ 1 ] ? parseInt ( args [ 1 ] , 10 ) : 2000 ;
136143 const count = args [ 2 ] ? parseInt ( args [ 2 ] , 10 ) : 10 ;
@@ -232,7 +239,10 @@ async function connect(url?: string): Promise<void> {
232239 console . log ( `Connecting to ${ serverUrl } ...` ) ;
233240
234241 try {
235- // Create a new client with form elicitation capability
242+ // Create task store for client-side task support
243+ const clientTaskStore = new InMemoryTaskStore ( ) ;
244+
245+ // Create a new client with form elicitation capability and task support
236246 client = new Client (
237247 {
238248 name : 'example-client' ,
@@ -242,25 +252,46 @@ async function connect(url?: string): Promise<void> {
242252 capabilities : {
243253 elicitation : {
244254 form : { }
255+ } ,
256+ tasks : {
257+ requests : {
258+ elicitation : {
259+ create : { }
260+ }
261+ }
245262 }
246- }
263+ } ,
264+ taskStore : clientTaskStore
247265 }
248266 ) ;
249267 client . onerror = error => {
250268 console . error ( '\x1b[31mClient error:' , error , '\x1b[0m' ) ;
251269 } ;
252270
253- // Set up elicitation request handler with proper validation
254- client . setRequestHandler ( ElicitRequestSchema , async request => {
271+ // Set up elicitation request handler with proper validation and task support
272+ client . setRequestHandler ( ElicitRequestSchema , async ( request , extra ) => {
255273 if ( request . params . mode !== 'form' ) {
256274 throw new McpError ( ErrorCode . InvalidParams , `Unsupported elicitation mode: ${ request . params . mode } ` ) ;
257275 }
258276 console . log ( '\n🔔 Elicitation (form) Request Received:' ) ;
259277 console . log ( `Message: ${ request . params . message } ` ) ;
260278 console . log ( `Related Task: ${ request . params . _meta ?. [ RELATED_TASK_META_KEY ] ?. taskId } ` ) ;
279+ console . log ( `Task Creation Requested: ${ request . params . task ? 'yes' : 'no' } ` ) ;
261280 console . log ( 'Requested Schema:' ) ;
262281 console . log ( JSON . stringify ( request . params . requestedSchema , null , 2 ) ) ;
263282
283+ // Helper to return result, optionally creating a task if requested
284+ const returnResult = async ( result : { action : 'accept' | 'decline' | 'cancel' ; content ?: Record < string , unknown > } ) => {
285+ if ( request . params . task && extra . taskStore ) {
286+ // Create a task and store the result
287+ const task = await extra . taskStore . createTask ( { ttl : extra . taskRequestedTtl } ) ;
288+ await extra . taskStore . storeTaskResult ( task . taskId , 'completed' , result ) ;
289+ console . log ( `📋 Created client-side task: ${ task . taskId } ` ) ;
290+ return { task } ;
291+ }
292+ return result ;
293+ } ;
294+
264295 const schema = request . params . requestedSchema ;
265296 const properties = schema . properties ;
266297 const required = schema . required || [ ] ;
@@ -381,7 +412,7 @@ async function connect(url?: string): Promise<void> {
381412 }
382413
383414 if ( inputCancelled ) {
384- return { action : 'cancel' } ;
415+ return returnResult ( { action : 'cancel' } ) ;
385416 }
386417
387418 // If we didn't complete all fields due to an error, try again
@@ -394,7 +425,7 @@ async function connect(url?: string): Promise<void> {
394425 continue ;
395426 } else {
396427 console . log ( 'Maximum attempts reached. Declining request.' ) ;
397- return { action : 'decline' } ;
428+ return returnResult ( { action : 'decline' } ) ;
398429 }
399430 }
400431
@@ -412,7 +443,7 @@ async function connect(url?: string): Promise<void> {
412443 continue ;
413444 } else {
414445 console . log ( 'Maximum attempts reached. Declining request.' ) ;
415- return { action : 'decline' } ;
446+ return returnResult ( { action : 'decline' } ) ;
416447 }
417448 }
418449
@@ -426,25 +457,34 @@ async function connect(url?: string): Promise<void> {
426457 } ) ;
427458 } ) ;
428459
429- if ( confirmAnswer === 'yes' || confirmAnswer === 'y' ) {
430- return {
431- action : 'accept' ,
432- content
433- } ;
434- } else if ( confirmAnswer === 'cancel' || confirmAnswer === 'c' ) {
435- return { action : 'cancel' } ;
436- } else if ( confirmAnswer === 'no' || confirmAnswer === 'n' ) {
437- if ( attempts < maxAttempts ) {
438- console . log ( 'Please re-enter the information...' ) ;
439- continue ;
440- } else {
441- return { action : 'decline' } ;
460+ switch ( confirmAnswer ) {
461+ case 'yes' :
462+ case 'y' : {
463+ return returnResult ( {
464+ action : 'accept' ,
465+ content
466+ } ) ;
467+ }
468+ case 'cancel' :
469+ case 'c' : {
470+ return returnResult ( { action : 'cancel' } ) ;
471+ }
472+ case 'no' :
473+ case 'n' : {
474+ if ( attempts < maxAttempts ) {
475+ console . log ( 'Please re-enter the information...' ) ;
476+ continue ;
477+ } else {
478+ return returnResult ( { action : 'decline' } ) ;
479+ }
480+
481+ break ;
442482 }
443483 }
444484 }
445485
446486 console . log ( 'Maximum attempts reached. Declining request.' ) ;
447- return { action : 'decline' } ;
487+ return returnResult ( { action : 'decline' } ) ;
448488 } ) ;
449489
450490 transport = new StreamableHTTPClientTransport ( new URL ( serverUrl ) , {
@@ -641,6 +681,12 @@ async function callCollectInfoTool(infoType: string): Promise<void> {
641681 await callTool ( 'collect-user-info' , { infoType } ) ;
642682}
643683
684+ async function callCollectInfoWithTask ( infoType : string ) : Promise < void > {
685+ console . log ( `\n🔄 Testing bidirectional task support with collect-user-info-task tool (${ infoType } )...` ) ;
686+ console . log ( 'This will create a task on the server, which will elicit input and create a task on the client.\n' ) ;
687+ await callToolTask ( 'collect-user-info-task' , { infoType } ) ;
688+ }
689+
644690async function startNotifications ( interval : number , count : number ) : Promise < void > {
645691 console . log ( `Starting notification stream: interval=${ interval } ms, count=${ count || 'unlimited' } ` ) ;
646692 await callTool ( 'start-notification-stream' , { interval, count } ) ;
0 commit comments