@@ -5,8 +5,10 @@ import {
55 GetTaskResult ,
66 Task ,
77 ElicitResultSchema ,
8+ ServerRequest ,
89} from "@modelcontextprotocol/sdk/types.js" ;
910import { CreateTaskResult } from "@modelcontextprotocol/sdk/experimental" ;
11+ import type { AnySchema , SchemaOutput } from "@modelcontextprotocol/sdk/server/zod-compat.js" ;
1012
1113// Tool input schema
1214const SimulateResearchQuerySchema = z . object ( {
@@ -36,7 +38,6 @@ interface ResearchState {
3638 ambiguous : boolean ;
3739 currentStage : number ;
3840 clarification ?: string ;
39- waitingForClarification : boolean ;
4041 completed : boolean ;
4142 result ?: CallToolResult ;
4243}
@@ -47,6 +48,7 @@ const researchStates = new Map<string, ResearchState>();
4748/**
4849 * Runs the background research process.
4950 * Updates task status as it progresses through stages.
51+ * If clarification is needed, sends elicitation request directly.
5052 */
5153async function runResearchProcess (
5254 taskId : string ,
@@ -62,7 +64,12 @@ async function runResearchProcess(
6264 status : "completed" | "failed" ,
6365 result : CallToolResult
6466 ) => Promise < void > ;
65- }
67+ } ,
68+ sendRequest : < U extends AnySchema > (
69+ request : ServerRequest ,
70+ resultSchema : U ,
71+ options ?: { timeout ?: number }
72+ ) => Promise < SchemaOutput < U > >
6673) : Promise < void > {
6774 const state = researchStates . get ( taskId ) ;
6875 if ( ! state ) return ;
@@ -79,14 +86,59 @@ async function runResearchProcess(
7986
8087 // At synthesis stage (index 2), check if clarification is needed
8188 if ( i === 2 && state . ambiguous && ! state . clarification ) {
82- state . waitingForClarification = true ;
89+ // Update status to show we're requesting input
8390 await taskStore . updateTaskStatus (
8491 taskId ,
8592 "input_required" ,
86- `Found multiple interpretations for "${ state . topic } ". Please clarify your intent.`
93+ `Found multiple interpretations for "${ state . topic } ". Requesting clarification...`
94+ ) ;
95+
96+ // Send elicitation directly and await response
97+ const elicitationResult = await sendRequest (
98+ {
99+ method : "elicitation/create" ,
100+ params : {
101+ message : `The research query "${ state . topic } " could have multiple interpretations. Please clarify what you're looking for:` ,
102+ requestedSchema : {
103+ type : "object" ,
104+ properties : {
105+ interpretation : {
106+ type : "string" ,
107+ title : "Clarification" ,
108+ description : "Which interpretation of the topic do you mean?" ,
109+ oneOf : getInterpretationsForTopic ( state . topic ) ,
110+ } ,
111+ } ,
112+ required : [ "interpretation" ] ,
113+ } ,
114+ } ,
115+ } ,
116+ ElicitResultSchema ,
117+ { timeout : 5 * 60 * 1000 /* 5 minutes */ }
118+ ) ;
119+
120+ // Process elicitation response
121+ if (
122+ elicitationResult . action === "accept" &&
123+ elicitationResult . content
124+ ) {
125+ state . clarification =
126+ ( elicitationResult . content as { interpretation ?: string } )
127+ . interpretation || "User accepted without selection" ;
128+ } else if ( elicitationResult . action === "decline" ) {
129+ state . clarification = "User declined - using default interpretation" ;
130+ } else {
131+ state . clarification = "User cancelled - using default interpretation" ;
132+ }
133+
134+ // Resume with working status
135+ await taskStore . updateTaskStatus (
136+ taskId ,
137+ "working" ,
138+ `Received clarification: "${ state . clarification } ". Continuing...`
87139 ) ;
88- // Wait for clarification - the getTaskResult handler will resume this
89- return ;
140+
141+ // Continue processing (no return - just keep going through the loop)
90142 }
91143
92144 // Simulate work for this stage
@@ -131,17 +183,18 @@ This tool demonstrates MCP's task-based execution pattern for long-running opera
1311833. Status progressed: \`working\` → ${ state . clarification ? `\`input_required\` → \`working\` → ` : "" } \`completed\`
1321844. Client calls \`tasks/result\` → Server returns this final result
133185
134- ${ state . clarification ? `**input_required Flow:**
135- When the query was ambiguous, the task paused with \`input_required \` status.
136- The client called \`tasks/result \` prematurely, which triggered an elicitation
137- request via the side-channel . After receiving clarification ("${ state . clarification } "),
138- the task resumed processing.
186+ ${ state . clarification ? `**Elicitation Flow:**
187+ When the query was ambiguous, the server sent an \`elicitation/create \` request
188+ directly to the client. The task status changed to \`input_required \` while
189+ awaiting user input . After receiving clarification ("${ state . clarification } "),
190+ the task resumed processing and completed .
139191` : "" }
140192**Key Concepts:**
141193- Tasks enable "call now, fetch later" patterns
142194- \`statusMessage\` provides human-readable progress updates
143195- Tasks have TTL (time-to-live) for automatic cleanup
144196- \`pollInterval\` suggests how often to check status
197+ - Elicitation requests can be sent directly during task execution
145198
146199*This is a simulated research report from the Everything MCP Server.*
147200` ;
@@ -178,7 +231,7 @@ export const registerSimulateResearchQueryTool = (server: McpServer) => {
178231 description :
179232 "Simulates a deep research operation that gathers, analyzes, and synthesizes information. " +
180233 "Demonstrates MCP task-based operations with progress through multiple stages. " +
181- "If 'ambiguous' is true and client supports elicitation, pauses for clarification (input_required status) ." ,
234+ "If 'ambiguous' is true and client supports elicitation, sends an elicitation request for clarification ." ,
182235 inputSchema : SimulateResearchQuerySchema ,
183236 execution : { taskSupport : "required" } ,
184237 } ,
@@ -200,20 +253,23 @@ export const registerSimulateResearchQueryTool = (server: McpServer) => {
200253 topic : validatedArgs . topic ,
201254 ambiguous : validatedArgs . ambiguous && clientSupportsElicitation ,
202255 currentStage : 0 ,
203- waitingForClarification : false ,
204256 completed : false ,
205257 } ;
206258 researchStates . set ( task . taskId , state ) ;
207259
208260 // Start background research (don't await - runs asynchronously)
209- runResearchProcess ( task . taskId , validatedArgs , extra . taskStore ) . catch (
210- ( error ) => {
211- console . error ( `Research task ${ task . taskId } failed:` , error ) ;
212- extra . taskStore
213- . updateTaskStatus ( task . taskId , "failed" , String ( error ) )
214- . catch ( console . error ) ;
215- }
216- ) ;
261+ // Pass sendRequest so elicitation can be sent directly from the background process
262+ runResearchProcess (
263+ task . taskId ,
264+ validatedArgs ,
265+ extra . taskStore ,
266+ extra . sendRequest
267+ ) . catch ( ( error ) => {
268+ console . error ( `Research task ${ task . taskId } failed:` , error ) ;
269+ extra . taskStore
270+ . updateTaskStatus ( task . taskId , "failed" , String ( error ) )
271+ . catch ( console . error ) ;
272+ } ) ;
217273
218274 return { task } ;
219275 } ,
@@ -228,77 +284,11 @@ export const registerSimulateResearchQueryTool = (server: McpServer) => {
228284 } ,
229285
230286 /**
231- * Returns the task result, or handles input_required via elicitation side-channel.
287+ * Returns the task result.
288+ * Elicitation is now handled directly in the background process.
232289 */
233290 getTaskResult : async ( args , extra ) : Promise < CallToolResult > => {
234- const task = await extra . taskStore . getTask ( extra . taskId ) ;
235- const state = researchStates . get ( extra . taskId ) ;
236-
237- // Handle input_required - use tasks/result as side-channel for elicitation
238- if ( task ?. status === "input_required" && state ?. waitingForClarification ) {
239- // Send elicitation request through the side-channel
240- const elicitationResult = await extra . sendRequest (
241- {
242- method : "elicitation/create" ,
243- params : {
244- message : `The research query "${ state . topic } " could have multiple interpretations. Please clarify what you're looking for:` ,
245- requestedSchema : {
246- type : "object" ,
247- properties : {
248- interpretation : {
249- type : "string" ,
250- title : "Clarification" ,
251- description : "Which interpretation of the topic do you mean?" ,
252- oneOf : getInterpretationsForTopic ( state . topic ) ,
253- } ,
254- } ,
255- required : [ "interpretation" ] ,
256- } ,
257- } ,
258- } ,
259- ElicitResultSchema ,
260- { timeout : 5 * 60 * 1000 /* 5 minutes */ }
261- ) ;
262-
263- // Process elicitation response
264- if (
265- elicitationResult . action === "accept" &&
266- elicitationResult . content
267- ) {
268- state . clarification =
269- ( elicitationResult . content as { interpretation ?: string } )
270- . interpretation || "User accepted without selection" ;
271- } else if ( elicitationResult . action === "decline" ) {
272- state . clarification = "User declined - using default interpretation" ;
273- } else {
274- state . clarification = "User cancelled - using default interpretation" ;
275- }
276-
277- state . waitingForClarification = false ;
278-
279- // Resume background processing from current stage
280- runResearchProcess ( extra . taskId , {
281- topic : state . topic ,
282- ambiguous : false , // Don't ask again
283- } , extra . taskStore ) . catch ( ( error ) => {
284- console . error ( `Research task ${ extra . taskId } failed:` , error ) ;
285- extra . taskStore
286- . updateTaskStatus ( extra . taskId , "failed" , String ( error ) )
287- . catch ( console . error ) ;
288- } ) ;
289-
290- // Return indication that work is resuming (client should poll again)
291- return {
292- content : [
293- {
294- type : "text" ,
295- text : `Resuming research with clarification: "${ state . clarification } "` ,
296- } ,
297- ] ,
298- } ;
299- }
300-
301- // Normal case: return the stored result
291+ // Return the stored result
302292 const result = await extra . taskStore . getTaskResult ( extra . taskId ) ;
303293
304294 // Clean up state
0 commit comments