Skip to content

Commit d10d116

Browse files
committed
Write end-to-end example for a task-augmented tool call with elicitation under MRTR
1 parent a027d30 commit d10d116

2 files changed

Lines changed: 374 additions & 0 deletions

File tree

docs/community/seps/2339-task-continuity.mdx

Lines changed: 187 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

seps/2339-task-continuity.md

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,193 @@ And finally, the server will accept that client request with a standard task sta
236236
}
237237
```
238238

239+
A complete example of a task-augmented tool call with an elicitation under MRTR is provided in more detail below:
240+
241+
<details>
242+
243+
Consider a simple task-augmented tool call, `hello_world`, requiring an elicitation for the user to provide their name. The tool itself takes no arguments.
244+
245+
To invoke this tool, the client makes a `CallToolRequest` as follows:
246+
247+
```json
248+
{
249+
"jsonrpc": "2.0",
250+
"id": 2,
251+
"method": "tools/call",
252+
"params": {
253+
"name": "hello_world",
254+
"arguments": {}
255+
},
256+
"task": {
257+
"ttl": 30000
258+
}
259+
}
260+
```
261+
262+
The server recognizes this as a task-augmented request and immediately returns a `CreateTaskResult`:
263+
264+
```json
265+
{
266+
"jsonrpc": "2.0",
267+
"id": 2,
268+
"result": {
269+
"task": {
270+
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
271+
"status": "working",
272+
"createdAt": "2025-11-25T10:30:00Z",
273+
"lastUpdatedAt": "2025-11-25T10:50:00Z",
274+
"ttl": 30000,
275+
"pollInterval": 5000
276+
}
277+
}
278+
}
279+
```
280+
281+
Once the client receives the `CreateTaskResult`, it begins polling `tasks/continue`:
282+
283+
```json
284+
{
285+
"jsonrpc": "2.0",
286+
"id": 3,
287+
"method": "tasks/continue",
288+
"params": {
289+
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
290+
}
291+
}
292+
```
293+
294+
On each request while the task is in a `"working"` status, the server returns a regular task response:
295+
296+
```json
297+
{
298+
"jsonrpc": "2.0",
299+
"id": 3,
300+
"result": {
301+
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
302+
"status": "working",
303+
"createdAt": "2025-11-25T10:30:00Z",
304+
"lastUpdatedAt": "2025-11-25T10:50:00Z",
305+
"ttl": 30000,
306+
"pollInterval": 5000
307+
}
308+
}
309+
```
310+
311+
Eventually, the server reaches the point at which it needs to send an elicitation to the user. It sets the task status to `"input_required"` to signal this. On the next `tasks/continue` request from the client, the server sends the elicitation payload via an `IncompleteResult`, following standard MRTR semantics. Note that the task info is not present in this response, which conforms to typical behavior under MRTR. If the client were to send a `tasks/get` request during this time, it would see a regular status response with `"input_required"` (without the embedded `inputRequests`).
312+
313+
```json
314+
{
315+
"jsonrpc": "2.0",
316+
"id": 4,
317+
"method": "tasks/continue",
318+
"params": {
319+
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
320+
}
321+
}
322+
```
323+
324+
```json
325+
{
326+
"id": 4,
327+
"jsonrpc": "2.0",
328+
"result": {
329+
"inputRequests": {
330+
"name": {
331+
"method": "elicitation/create",
332+
"params": {
333+
"mode": "form",
334+
"message": "Please enter your name.",
335+
"requestedSchema": {
336+
"type": "object",
337+
"properties": {
338+
"name": { "type": "string" }
339+
},
340+
"required": ["name"]
341+
}
342+
}
343+
}
344+
}
345+
}
346+
}
347+
```
348+
349+
The user enters their name, and the client repeats the `tasks/continue` request with the satisfied information:
350+
351+
```json
352+
{
353+
"jsonrpc": "2.0",
354+
"id": 5,
355+
"method": "tasks/continue",
356+
"params": {
357+
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
358+
"inputResponses": {
359+
"name": {
360+
"action": "accept",
361+
"content": {
362+
"input": "Luca"
363+
}
364+
}
365+
}
366+
}
367+
}
368+
```
369+
370+
With the elicitation fulfilled and no other outstanding requests to send, the server moves the task back into the `"working"` status:
371+
372+
```json
373+
{
374+
"jsonrpc": "2.0",
375+
"id": 5,
376+
"result": {
377+
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
378+
"status": "working",
379+
"createdAt": "2025-11-25T10:30:00Z",
380+
"lastUpdatedAt": "2025-11-25T10:50:00Z",
381+
"ttl": 30000,
382+
"pollInterval": 5000
383+
}
384+
}
385+
```
386+
387+
Eventually, the server completes the request, so it stores the final `CallToolResult` and moves the task into the `"completed"` status. On the next `tasks/continue` request, the server sends the final tool result inlined into the task object:
388+
389+
```json
390+
{
391+
"jsonrpc": "2.0",
392+
"id": 6,
393+
"method": "tasks/continue",
394+
"params": {
395+
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
396+
}
397+
}
398+
```
399+
400+
```json
401+
{
402+
"jsonrpc": "2.0",
403+
"id": 6,
404+
"result": {
405+
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
406+
"status": "completed",
407+
"createdAt": "2025-11-25T10:30:00Z",
408+
"lastUpdatedAt": "2025-11-25T10:50:00Z",
409+
"ttl": 30000,
410+
"pollInterval": 5000,
411+
"result": {
412+
"content": [
413+
{
414+
"type": "text",
415+
"text": "Hello, Luca!"
416+
}
417+
],
418+
"isError": false
419+
}
420+
}
421+
}
422+
```
423+
424+
</details>
425+
239426
### Keeping `tasks/get`
240427

241428
If we're introducing a new method that encapsulates the entire task-polling lifecycle, **why should we keep `tasks/get`**?

0 commit comments

Comments
 (0)