1: <?php
2:
3: declare(strict_types=1);
4:
5: /**
6: * This file is part of the Nexus MCP SDK package.
7: *
8: * (c) 2026 John Paul E. Balandan, CPA <paulbalandan@gmail.com>
9: *
10: * For the full copyright and license information, please view
11: * the LICENSE file that was distributed with this source code.
12: */
13:
14: namespace Nexus\Mcp\Extension\Tasks\Server\Handler;
15:
16: use Nexus\Mcp\Core\Exception\InvalidParamsException;
17: use Nexus\Mcp\Core\Handler\AbstractContext;
18: use Nexus\Mcp\Core\Handler\RequestHandlerInterface;
19: use Nexus\Mcp\Core\Schema\JsonRpc\JsonRpcRequest;
20: use Nexus\Mcp\Core\Schema\Request\CallToolRequest;
21: use Nexus\Mcp\Core\Schema\RequestParams\CallToolRequestParams;
22: use Nexus\Mcp\Core\Schema\Result\EmptyResult;
23: use Nexus\Mcp\Core\Schema\Result\InputResponse;
24: use Nexus\Mcp\Extension\Tasks\Schema\Enum\TaskStatus;
25: use Nexus\Mcp\Extension\Tasks\Schema\Request\UpdateTaskRequest;
26: use Nexus\Mcp\Extension\Tasks\Server\Store\TaskStoreInterface;
27: use Nexus\Mcp\Extension\Tasks\Server\ToolTaskRunner;
28: use Nexus\Mcp\Server\ServerContext;
29:
30: /**
31: * Handles the `tasks/update` request, re-dispatching the tool call once no input request is outstanding.
32: *
33: * @implements RequestHandlerInterface<'tasks/update', EmptyResult, ServerContext>
34: */
35: final readonly class UpdateTaskRequestHandler implements RequestHandlerInterface
36: {
37: public function __construct(
38: private TaskStoreInterface $store,
39: private ToolTaskRunner $runner,
40: ) {
41: }
42:
43: #[\Override]
44: public function handle(JsonRpcRequest $request, AbstractContext $context): EmptyResult
45: {
46: \assert($request instanceof UpdateTaskRequest);
47: \assert($context instanceof ServerContext);
48:
49: $record = $this->store->findTask($request->params->taskId);
50:
51: if (null === $record) {
52: throw new InvalidParamsException($context->requestId, '"params.taskId" does not name a known task.');
53: }
54:
55: $accepted = [];
56:
57: foreach (array_intersect_key($request->params->inputResponses, $record->pendingInputRequests) as $key => $response) {
58: if (! $response instanceof InputResponse) {
59: throw new InvalidParamsException($context->requestId, \sprintf('"params.inputResponses" entry "%s" is not a valid input response.', $key));
60: }
61:
62: $accepted[$key] = $response;
63: }
64:
65: $updated = $this->store->resolveInputRequests($record->taskId, $accepted);
66:
67: if (null === $updated || TaskStatus::InputRequired !== $updated->status || [] !== $updated->pendingInputRequests) {
68: return new EmptyResult();
69: }
70:
71: $this->runner->ensureCapacity($context->requestId);
72: $this->store->trySetWorking($updated->taskId);
73:
74: $call = new CallToolRequest(
75: id: $request->id,
76: params: new CallToolRequestParams(
77: name: $updated->toolName,
78: meta: $context->meta,
79: arguments: $updated->arguments,
80: ),
81: );
82:
83: $this->runner->startTask(
84: $updated->taskId,
85: $call,
86: $context,
87: inputResponses: $updated->inputResponses,
88: requestState: $updated->requestState,
89: );
90:
91: return new EmptyResult();
92: }
93: }
94: