Skip to content

Commit 420a4b4

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

2 files changed

Lines changed: 372 additions & 0 deletions

File tree

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

Lines changed: 186 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: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,192 @@ 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+
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.
243+
244+
To invoke this tool, the client makes a `CallToolRequest` as follows:
245+
246+
```json
247+
{
248+
"jsonrpc": "2.0",
249+
"id": 2,
250+
"method": "tools/call",
251+
"params": {
252+
"name": "hello_world",
253+
"arguments": {}
254+
},
255+
"task": {
256+
"ttl": 30000
257+
}
258+
}
259+
```
260+
261+
The server recognizes this as a task-augmented request and immediately returns a `CreateTaskResult`:
262+
263+
```json
264+
{
265+
"jsonrpc": "2.0",
266+
"id": 2,
267+
"result": {
268+
"task": {
269+
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
270+
"status": "working",
271+
"createdAt": "2025-11-25T10:30:00Z",
272+
"lastUpdatedAt": "2025-11-25T10:50:00Z",
273+
"ttl": 30000,
274+
"pollInterval": 5000
275+
}
276+
}
277+
}
278+
```
279+
280+
Once the client receives the `CreateTaskResult`, it begins polling `tasks/continue`:
281+
282+
```json
283+
{
284+
"jsonrpc": "2.0",
285+
"id": 3,
286+
"method": "tasks/continue",
287+
"params": {
288+
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
289+
}
290+
}
291+
```
292+
293+
On each request while the task is in a `"working"` status, the server returns a regular task response:
294+
295+
```json
296+
{
297+
"jsonrpc": "2.0",
298+
"id": 3,
299+
"result": {
300+
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
301+
"status": "working",
302+
"createdAt": "2025-11-25T10:30:00Z",
303+
"lastUpdatedAt": "2025-11-25T10:50:00Z",
304+
"ttl": 30000,
305+
"pollInterval": 5000
306+
}
307+
}
308+
```
309+
310+
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`).
311+
312+
```json
313+
{
314+
"jsonrpc": "2.0",
315+
"id": 4,
316+
"method": "tasks/continue",
317+
"params": {
318+
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
319+
}
320+
}
321+
```
322+
323+
```json
324+
{
325+
"id": 4,
326+
"jsonrpc": "2.0",
327+
"result": {
328+
"inputRequests": {
329+
"name": {
330+
"method": "elicitation/create",
331+
"params": {
332+
"mode": "form",
333+
"message": "Please enter your name.",
334+
"requestedSchema": {
335+
"type": "object",
336+
"properties": {
337+
"name": { "type": "string" }
338+
},
339+
"required": ["name"]
340+
}
341+
}
342+
}
343+
}
344+
}
345+
}
346+
```
347+
348+
The user enters their name, and the client repeats the `tasks/continue` request with the satisfied information:
349+
350+
```json
351+
{
352+
"jsonrpc": "2.0",
353+
"id": 5,
354+
"method": "tasks/continue",
355+
"params": {
356+
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
357+
"inputResponses": {
358+
"name": {
359+
"action": "accept",
360+
"content": {
361+
"input": "Luca"
362+
}
363+
}
364+
}
365+
}
366+
}
367+
```
368+
369+
With the elicitation fulfilled and no other outstanding requests to send, the server moves the task back into the `"working"` status:
370+
371+
```json
372+
{
373+
"jsonrpc": "2.0",
374+
"id": 5,
375+
"result": {
376+
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
377+
"status": "working",
378+
"createdAt": "2025-11-25T10:30:00Z",
379+
"lastUpdatedAt": "2025-11-25T10:50:00Z",
380+
"ttl": 30000,
381+
"pollInterval": 5000
382+
}
383+
}
384+
```
385+
386+
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:
387+
388+
```json
389+
{
390+
"jsonrpc": "2.0",
391+
"id": 6,
392+
"method": "tasks/continue",
393+
"params": {
394+
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
395+
}
396+
}
397+
```
398+
399+
```json
400+
{
401+
"jsonrpc": "2.0",
402+
"id": 6,
403+
"result": {
404+
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
405+
"status": "completed",
406+
"createdAt": "2025-11-25T10:30:00Z",
407+
"lastUpdatedAt": "2025-11-25T10:50:00Z",
408+
"ttl": 30000,
409+
"pollInterval": 5000,
410+
"result": {
411+
"content": [
412+
{
413+
"type": "text",
414+
"text": "Hello, Luca!"
415+
}
416+
],
417+
"isError": false
418+
}
419+
}
420+
}
421+
```
422+
423+
</details>
424+
239425
### Keeping `tasks/get`
240426

241427
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)