The MCP Tasks Extension

summarized

TLDR

The new MCP Tasks Extension replaces the experimental 1120 spec with a simpler, stateless design that removes blocking calls and reduces capability checks from three to one. The earlier version required an eight-step lifecycle with a blocking connection for user input; the new version uses a polling loop with a separate task/update method, making it more secure and amenable to stateless servers. This is a breaking change — old task/result and task/list methods are deleted, and servers must shim backwards compatibility if needed.

Key points

  • MCP's baseline model is synchronous; the tasks extension handles work that doesn't finish in a single request, such as batch migrations, model training, or multi-step workflows.
  • The earlier experimental version (1120 spec) defined tasks in the core specification with symmetric client/server roles and four methods: get, result, cancel, list.
  • The 1120 spec had three issues: a three-layer capability negotiation, a blocking task/result call that kept a connection open indefinitely, and a security risk where task/list could expose all tasks on the server.
  • The new version (720 spec) moves tasks to a separate extension (io.modelcontextprotocol/tasks) and is not backwards compatible.
  • The new lifecycle has a single capability check, a polling loop using task/get, and a non-blocking task/update method for the server to request user input; the result is returned directly in the task/get response when completed.
  • New methods are task/get, task/update, and task/cancel; task/result and task/list have been removed.
  • If a client does not declare task support, the server falls back to a blocking synchronous call instead of returning a task handle.
  • The demo shows capability negotiation, task creation returning a task ID, polling for status, cancellation (not guaranteed immediate), and error handling where business logic failures are returned inside a completed task result.
  • tools
  • MCP (Model Context Protocol)
  • MCP Tasks Extension (io.modelcontextprotocol/tasks)

Techniques

  • polling loop
  • stateless protocol
  • capability negotiation
  • async task handling
Transcript (captions)

0:01 [music] [music] All right, our next guest want to be bring on Vicram. He's a developer advocate at Mel Empire, a longtime

0:17 contributor to open source community and has written a lot of it documentation out there and working a lot of inantic workflows and here to talk a lot about orchestration for agents forms.

0:32 >> Hi, hello everyone. >> Great to have you here. >> See your screen. >> Yep, we got it. And you're going presentation mode. All right, we can see

0:42 your screen. The floor is yours. >> Awesome. Okay. Hey everyone, thank you for joining this talk. I actually want to talk today about something. It sounds narrow, but it actually covers a lot of

0:54 ground. It's the tasks extension and specifically how MCP handles work that doesn't finish in a single request. And we're going to look at two versions of this. the earlier version of this from

1:06 the 1120 spec and then the latest one in this 720 spec. So my name is Vikram Wasani. I'm speaking to you from Mumbai. I've been just a little bit about me. I've been doing independent technical

1:18 content developer advocacy open source consulting since 2003. So it's been a while. Along the way I've written some books. I've written a lot of tutorials. You may have seen some of them online.

1:30 And of course over the last years most people have been experimenting a lot with agents and AI and CH workflows and been a speaker at a few conferences. So AI deb Europe some screen position by

1:45 one and a master of the AI as well. That's just a little bit about me and let's move straight into the talk. So the task protocol first question why does this exist

2:03 and it's let's start with that. So the baseline model in MCP is that it's synchronous. A client sends a request server sends back a result and that's just the way it works. Then this is fine

2:16 for the majority of calls. occuring a database, hitting an API, reading a file. All of this is pretty fast under a second usually. But there are some things which aren't that fast. Let's say

2:27 you're doing a batch migration or a a batch export. You're training a model. You've got a multi-step workflow that needs to wait and think and then action. So all of these if you try to do them

2:40 synchronously, you get a connection that just once you initiate the tool call, they're just waiting. There's no progress reporting. There's no cancellation. There's no way for the

2:51 server to stop and ask a follow-up question in the middle of the poll. Before tasks existed, really you were on your own. Everyone created their own solution. This and everyone had a

3:05 different version of it with different servers. you could agree on what was the standard way, standard pattern work so on and tasks which was trying to produce a standard at the protocol level itself.

3:21 We'll talk first about the previous version the 1125 spec and this is where it started. Tasks were introduced directly in the core specification but not experimental right from day one. So

3:37 in this previous version it was symmetric. The spec defines a requesttor and a receiver and that can be the client or the server and either of them can create or execute a task. So it's

3:50 broader than you might have thought at first read. It's not just for tool calls. It can also be the server initiating a request like a sampling request or asking for user input

4:01 something which is a so it's a birectional concept over here and it was defined with four methods they're on the screen get result cancel and list so can dig into this a little bit more and I

4:17 have on the screen what the task life cycle was and we can walk through this step by step because it's as you can See, it's a ladder with a lot of rounds on it. And what is not on the screen

4:29 actually the what I call step zero is the initial phase where the client and the server are negotiating capabilities during the initialized call. And there are three layers to this actually. The

4:41 server first has to declare whether it supports tool calls which whether it supports tasks in tool calls. Then the second layer is that each individual tool can declare its own support and the

4:56 client can only discover this by calling tools/list and then separately you have operation level capabilities for list and cancel operations. So before you can get

5:08 started just at the initialization stage the client is already having to check three different places just to know if it's safe to attach a task two or above. So assuming it is go to step one the

5:23 flag will call a tool and it includes a task field with a TTL and the server will come back immediately with a create task result. So that's with a task ID. There's an object with a task ID and a

5:35 status of working and this is non-blocking. Step two is a polling loop. The client will pull it'll call task gets over and over again and check that the task is

5:47 still working. And this goes as many times as needed. Step three, it doesn't happen all the time, but when it does, this is built in. Maybe the server needs input from the user. Now the client will

6:00 pull again, but this time the status is not working. It's input required and how does the client respond to this? The client has to call task/ result and that is a blocking call. So that connection

6:14 stays open and while the connection is open the server will pull down and will push an elicitation create request. That's the question that it wants to be answered and it has the task ID so the

6:27 client knows which task it belongs to. That connection stays open until the client answers and it will send the response or the answer back on the same connection and close it. And then we go

6:38 back to the normal polling loop with the status of working until finally the task is actually completed. And when the status comes back as completed, the client can call task/ result which is

6:53 last. If you look at the last two rounds task/ result and this time the task is done and so you get the actual result on it. So that's roughly eight steps and one of them has the client opening a

7:07 connection and keeping it open without any way of knowing how long it'll last. So there's a block over there. This was round one of the approach and then in the latest specification it's

7:24 been changed a bit. So now so just to show before I come to this side three three real issues with the previous one the three layer capability negotiation at the top the blocking result call and

7:39 the task list which actually had a security issue where it was potentially possible for a plan to get a list of all tasks which are running on the server not just the task for itself. And so now

7:53 in se in the new protocol specification tasks is not in core at all. It's now it's now been separated into its own extension and this is an extension with under io.model context protocol/tasks

8:09 and it's got quite a few changes. So there is now one capability check not three and there's also change in the ownership model. The client now signals that it can handle tasks but the server

8:24 is the one that decides whether to use a task or also one important point is not backwards compatible there is a break from the previous version and I'll show

8:35 you why there is a break there. So diving into it a little bit more. So here we have the new life cycle not there are some similarities there are some differences so we just walk

8:50 through the differences so step zero is is the the threeear check capabilities is gone there's just one capability check now the first two steps are the same as before there's a tool called the

9:04 server will return a task handle and the client will pull and to check the status of the j of the task And this continues in a loop. Now the pre in the previous one we had

9:18 the issue of the server requiring information from the client. And in that case we what we talked about was that the client would make a task/res call and that would be a blocking call as a

9:31 birectional for birectional communication to answer the server's question that is no longer there. Now what happens is if the server has a requirement for user input, it will

9:42 actually notify the client of this in the task/get call. So instead of it the status being working, the status will change to input required and the protocol now defines a new method

9:57 task/updates which is used to send back send the response back to the server. So there's no longer an open connection which is blocking. is just a normal request

10:07 response and that once once the response is sent back to the server the task status changes back to working you continue polling and you continue polling until

10:20 the task status is completed again there's a change here once the task status is shown as completed the result of the task comes directly in the response there's no separate call to

10:31 task result which is what was there previously now if you Take a look just visually if you look at this ladder versus the one we had earlier you can see it's a much shorter ladder it's a

10:42 much simpler one and one of the key things of course and it's a global thing is that now it's also this this approach is a lot more amanable to the stateless stateless approach that the new NCB

10:56 protocol is encouraging quick couple of minutes on the methods the new methods themselves so you have t/get get which is what you're using to pull and get status and also gives you

11:09 the result at the end. There's star/update which is what you would use to send a request send a response to the server when it asks a question. There's star/cancel which you use to cancel an

11:21 in progress task and two methods from previous and not deleted result and list. Also worth mentioning here that the new protocol specification separates errors.

11:35 So for example, if the protocol itself breaks, so for example, if it's a bad task ID or you're calling a a rule which doesn't exist, that changes that causes the task to fail. But if the task

11:48 actually completes and just reports a problem with with its internal business logic. So say for example a bad file path or maybe there's failure in a downstream API call that's not

11:59 considered a failure. It's still a completed task but the error is carried inside the result. I'll show you that in the demo at the end as well. So very quick comparison of the two.

12:12 This is people who are following along. Maybe this is a good place if you want to take a screenshot for reference if you're you're working on this. You're going to be building on this. Um

12:25 so I'll kick down the screen for a second and moving on. So I'm going to quickly run through a demo of the new tasks implementation.

12:37 Give me a second to swap screens. Hopefully everyone can see this. Just looking for a quick message in the chat. Make sure you can see my ID please. Someone give me a thumbs up.

13:13 Okay, I'm assuming that you guys can see the screen. So, I will move on. All right. So, over here I have I've got FCP server running created against the new protocol. And what we're going to do

13:26 is we're going to run through a few requests. And so, first one capability negotiation. Just going to run this in here. This is a server discover method. As you can see

13:38 over there and what we're going to see is what does the server show us and we see also that it does support tasks over here and the client itself has also indicated to the server that it has

13:52 support for tasks through this particular client capabilities. So now that we know both client and servers or both tasks, let's see what tools are available

14:07 and okay that search once and we see that there is a tool available called generate report. This is a tool that I created just for this demo and what it does is it creates a report on a

14:18 simulated reporter. Let's run. Let's actually ask for this tool to run. And what I have here is a request a tool call here. And what we're doing is we're

14:34 calling the generated report. We're giving it a topic and we're asking for the report in a certain style. Let's see when I run this. And so you see immediately this returns. It's not

14:46 blocking. It's returning immediately and it's giving me a task ID over here and you see also the status which is shown as working. So let's take this task ID of that

15:00 and we can check the status of the task. So, and now I'm calling task/get and I'm passing to it the in the MCP name header and also in the body of the request I'm

15:19 passing the task ID and we see that we're getting the status. It's still showing that it's working. We pull it again in a few seconds. Yeah, let's try again. Yeah, it's still

15:36 working. We can have a look at the server side as well. You see that over here, this is actually the the server is actually doing its work. It's come back and the server the task is completed and

15:47 when I check now when I pull with task that I see as result type is complete. The status is completed and actually the result is also in line directly in the response object. This is the simulated

16:00 report that it's created. Let's try one more. So it's going to run this task again. And while that's running, we take the task ID

16:18 and what we can show is the task cancel. So if you're if you decide you want to stop that task in between, there is now this task cancel method which you can call and I'll show you that as well.

16:35 And we see here on the server side you'll see that it started the task but now I issued a cancel command and so it did cancel it. Worth noting in the spec that the cancellation is not guaranteed.

16:46 It's actually up to the server to define how the cancellation is end. So it might return an acknowledgement and cancel later or it might cancel immediate. Couple of things worth noting here. What

16:58 happens if the client does not actually support tasks? It's not part of the client capabilities. And so here's a request. And you'll notice in this request that in the client capabilities,

17:11 unlike in the previous one where I've mentioned the tasks extension in this one, I am not stating that it can support tasks. And in let's see what happens in this case. So in this case,

17:24 it actually turns into a blocking call. It's not returning a task ID. It's just waiting. And if we check on the server, we'll see that the task is actually in progress. And it will block and wait

17:36 until the task is complete. So it's actually not really doing it in the async. We can that. And finally, a last piece of information is what happens if you have a server

17:51 which is trying to connect to this and using the old style. And so have this as well. So in this case we're using the 1125 version of the protocol. You can see that in this

18:03 request see what happens. Just copy paste this. And you can see that it did initialize. It's a stateless server so there is no session ID but it accepted the request.

18:31 What happens if you do call for the same tool again not using the new call and you'll see that it's the same as what happened if you don't pass task capabilities. It's going to block it is

18:45 still running over here. It is started the call and it will continue in a block. And one last interesting one, what happens if you asked for results using

19:04 the old style call? And you see that straight away returns an error that this call is no longer available. And this because as I said in one of the previous slides it's been deleted from the new

19:18 version of coming back to the slides. Just a couple more minutes here. It's done. >> Yep. Yeah. Almost there.

19:35 >> So just a couple of quick takeaways. First of all, this is now a much smaller, more compact version of what we had with bore. The it's on purpose. There are fewer methods. It's genuinely

19:47 stateless. There's a strict separation now in reading and writing. It's also more secure. We don't no longer have task list, which add a potential security aspect to it where it could be

19:58 possible for a task list to show all tasks on the server, not just those associated to the specific client. No backwards compatibility. This is part of the spec. If you want to support earlier

20:11 clients or the earlier experimental version, you will need to shim it in your server itself. And the continuous iteration extensions progress independently of the main spec. You can

20:24 expect that there may be further changes on this before it gets rolled back into the code. I suspect this isn't the final three. So that's about it. Thank you so much

20:35 for your time. love to keep chatting about this. You can get in touch with me if you want on LinkedIn or GitHub. There are links here on the screen. Can scan the QR code that's QR code to my

20:46 LinkedIn. And if there's something interesting you want to talk about, happy to be available. And if you have any questions, please go ahead and ask. >> Yeah, thank you very much. Thanks for

20:58 the detail of not only tasks that were in the experimental seven months ago, but also where they moved to as a primitive now and one of the bigger changes in the release that got released

21:10 last week. >> Yeah. >> Don't see any questions right now, but if they pop up, I'll make sure to share them with you guys. So, thank you very

21:19 much for your time. >> Yeah, welcome. [music]

Frontier News · by Hyperjump Technology