Skip to content

Conversation

@ezynda3
Copy link
Contributor

@ezynda3 ezynda3 commented Jan 30, 2026

Description

This PR implements comprehensive support for task-augmented tools in the MCP Go SDK, allowing tools to execute asynchronously and report progress through the tasks subsystem.

Fixes #656

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • MCP spec compatibility implementation
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring (no functional changes)
  • Performance improvement
  • Tests only (no functional changes)
  • Other (please describe):

Checklist

  • My code follows the code style of this project
  • I have performed a self-review of my own code
  • I have added tests that prove my fix is effective or that my feature works
  • I have updated the documentation accordingly

MCP Spec Compliance

Key Features

Core Implementation

  • TaskToolHandlerFunc: New handler type for async task execution with TaskContext
  • ServerTaskTool: Extended tool struct with task execution capabilities
  • AddTaskTool/AddTaskTools: Methods to register task-augmented tools
  • Hybrid mode detection: Automatic detection of optional vs required task support

Task Management

  • Task status notifications (created, progress, completed, failed, cancelled)
  • Context cancellation handling for graceful task shutdown
  • RelatedTaskMeta helper functions for task metadata management
  • Pagination support for tasks/list endpoint

Observability & Limits

  • Task metrics and observability hooks (OnTaskStart, OnTaskComplete, OnTaskError)
  • Max concurrent tasks limit with configurable threshold
  • lastUpdatedAt field for task tracking

Documentation & Examples

  • Comprehensive README documentation for task-augmented tools
  • Full example implementation in examples/task_tool/

Additional Information

This implementation enables long-running tool operations to:

  • Execute asynchronously without blocking
  • Report progress updates to clients
  • Be cancelled gracefully
  • Track execution metrics

Summary by CodeRabbit

  • New Features

    • Asynchronous task-augmented tools with three support modes (Forbidden, Optional, Required)
    • Server-side task lifecycle: creation, polling, completion, cancellation, and status notifications
    • Task observability hooks and configurable max concurrent task limits with enforcement
    • Task TTL, expiration handling, and related-task / model-immediate-response metadata
  • Documentation

    • New examples and step-by-step guidance illustrating task-augmented tools and usage
  • Tests

    • Extensive unit and integration tests covering task flows, hooks, limits, and JSON response formats

✏️ Tip: You can customize this high-level summary in your review settings.

Add foundational types for task-augmented tool support per MCP spec 2025-11-25:

- Add TaskSupport type (string enum) with three constants:
  - TaskSupportForbidden: tool cannot be invoked as a task (default)
  - TaskSupportOptional: tool can be invoked either way
  - TaskSupportRequired: tool must be invoked as a task
- Add ToolExecution struct with TaskSupport field
- Update Tool struct to include optional Execution field
- Update Tool.MarshalJSON to serialize execution field when present
- Add comprehensive tests for marshaling behavior and constants

Related to #656
- Add Task.GetName() method to implement Named interface for pagination
- Update handleListTasks to sort tasks by TaskId and use listByPagination
- Add comprehensive pagination tests covering multiple pages and edge cases
- Add TestTask_GetName unit test

All tests pass with race detector, linter clean.
Add helper functions for associating messages with tasks per MCP spec:
- RelatedTaskMetaKey constant for standard metadata key
- RelatedTaskMeta() to create task metadata maps
- WithRelatedTask() to create Meta objects with task IDs

Includes comprehensive test coverage in mcp/tasks_test.go.

Part of #656 (TAS-14)
Add WithTaskSupport() function to configure task support modes for tools.
The function accepts TaskSupportForbidden, TaskSupportOptional, or
TaskSupportRequired values and properly initializes or updates the tool's
Execution field.

- Implement WithTaskSupport() in mcp/tools.go
- Add comprehensive test coverage in mcp/tools_test.go
- Test all three task support modes with JSON marshaling
- Test Execution initialization when nil
- Test preservation of existing Execution instance

Related to #656
…d tool calls

- Add Task *TaskParams field to CallToolParams struct to support task augmentation
- Add ToInt64Ptr() helper function in utils.go for creating int64 pointers
- Add comprehensive tests for CallToolRequest with task params:
  - Test unmarshaling with task params, without task params, and null task params
  - Test round-trip marshaling/unmarshaling to verify data preservation
- All tests pass with race detector, linter clean

This enables detection of task-augmented tool calls and unblocks:
- TAS-15: Support for optional task tools (hybrid mode)
- TAS-16: Validation of task support on required tools

Related to #656
Add LastUpdatedAt field to Task struct per MCP spec 2025-11-25 requirement.
The field is automatically set on task creation and updated whenever the
task status changes (completed, failed, or cancelled).

Changes:
- Add LastUpdatedAt string field to Task struct in mcp/types.go
- Initialize both CreatedAt and LastUpdatedAt in NewTask() (mcp/tasks.go)
- Update lastUpdatedAt in completeTask() when task completes or fails
- Update lastUpdatedAt in cancelTask() when task is cancelled
- Add comprehensive test coverage for timestamp behavior
- Enhance JSON marshaling tests to verify field presence

All tests pass, linter clean. Complies with MCP spec requirement that
tasks MUST include lastUpdatedAt ISO 8601 timestamp.
Implement validation in handleToolCall() to enforce that tools with
TaskSupportRequired cannot be called without task augmentation. Returns
METHOD_NOT_FOUND error with message 'tool X requires task augmentation'.

Add comprehensive test suite TestMCPServer_TaskSupportValidation with 7
test cases covering all task support modes (required, optional, forbidden).

Implements MCP spec 2025-11-25 requirement for task support validation.

Related to #656
Implement notifications/tasks/status to notify clients when task status
changes. Notifications are sent automatically when tasks complete, fail,
or are cancelled.

Changes:
- Add sendTaskStatusNotification() helper method that converts task data
  to notification params format
- Include required fields: taskId, status, createdAt, lastUpdatedAt
- Conditionally include optional fields: statusMessage, ttl, pollInterval
- Integrate notifications into completeTask() and cancelTask()
- Add comprehensive test suite with 7 test cases covering all scenarios

Complies with MCP spec 2025-11-25 task notification requirements.

Issue: #656
Add routing logic to detect when tools with TaskSupportOptional or
TaskSupportRequired are called with task parameters. Tools called with
task params now route to task-augmented execution path (placeholder
error until TAS-9 implements handleTaskAugmentedToolCall). Tools
called without task params continue to execute synchronously.

- Add shouldExecuteAsTask detection in handleToolCall()
- Add comprehensive test suite TestMCPServer_HybridModeDetection
- Update existing tests to expect task execution routing behavior

This provides the routing foundation for TAS-9 to implement actual
task-augmented tool execution.
- Add detection logic in handleToolCall() to identify task-augmented requests
- Route requests with task params to handleTaskAugmentedToolCall() when tool has TaskSupportOptional or TaskSupportRequired
- Add handleTaskAugmentedToolCall() stub method with TODO for TAS-9 implementation
- Completes TAS-8: routing infrastructure for task-augmented tool execution

Related to #656
Add comprehensive example showing task-augmented tools with three types:
- process_batch: TaskSupportRequired tool for batch processing
- analyze_data: TaskSupportOptional tool with sync/async modes
- quick_check: Regular synchronous tool for comparison

Includes detailed README with usage examples, HTTP testing instructions,
and task workflow explanation. Build constraint prevents compilation until
core implementation (TAS-4 through TAS-10) is complete.

Related to #656 (TAS-19)
…for async task execution

- Add TaskToolHandlerFunc type for asynchronous tool call handlers that return CreateTaskResult
- Add ServerTaskTool struct combining Tool with TaskToolHandlerFunc
- Add taskTools map to MCPServer for storing task-augmented tools
- Initialize taskTools map in NewMCPServer constructor
- Add comprehensive test suite validating type definitions and initialization

These type definitions provide the foundation for task-augmented tool execution (TAS-4, TAS-5).
Related to #656
Implement executeTaskTool method that executes task tool handlers
asynchronously. The method creates a cancellable context, stores the
cancel function for potential cancellation via tasks/cancel, executes
the handler in the background, and completes the task with result or error.

Implementation details:
- Creates cancellable context using context.WithCancel()
- Stores cancel func in task entry under lock before execution
- Executes TaskToolHandlerFunc and handles results/errors
- Calls completeTask() to update status and send notifications

Test coverage includes 6 comprehensive test cases:
- Successful task execution stores result
- Failed task execution stores error with proper status
- Task cancellation via context
- Cancel function storage timing (race condition test)
- Task status notification on completion
- Multiple concurrent task execution

All tests pass with race detector, linter clean.

This provides the core async execution mechanism for TAS-9
(handleTaskAugmentedToolCall).

Related to #656
Implement handleTaskAugmentedToolCall method that enables async execution
of tools with TaskSupport. The implementation:

- Checks both taskTools and regular tools maps for tool lookup
- Validates task support mode (optional/required)
- Generates UUID v4 task IDs and creates task entries
- Starts async execution via executeTaskTool or executeRegularToolAsTask
- Returns CreateTaskResult immediately wrapped in CallToolResult _meta field
- Fixes race condition by copying task before returning

Add executeRegularToolAsTask method to handle hybrid mode where regular
tools with TaskSupport are executed asynchronously.

Update test expectations in TestMCPServer_HybridModeDetection and
TestMCPServer_TaskSupportValidation to expect successful task creation
instead of 'not yet implemented' error.

Tools with TaskSupportOptional/Required can now be called with task
params and execute asynchronously with proper task tracking.
- Add Task-Augmented Tools section explaining async execution
- Document three TaskSupport modes (Forbidden, Optional, Required)
- Include code examples for TaskSupportRequired and TaskSupportOptional
- Explain task execution flow (call → task ID → async → polling → notifications)
- Add context cancellation and WithTaskCapabilities examples
- Update Examples section to highlight task_tool example
- Reorganize synchronous tools section for clarity
Update handleListTools to return both regular and task-augmented tools in tools/list response. Implementation iterates over both s.tools and s.taskTools maps, sorts all tool names alphabetically, and includes both types in the result.

- Extend capacity of tools slice to accommodate both tool types
- Collect tool names from both s.tools and s.taskTools maps
- Maintain alphabetical sorting across all tools
- Protected by existing toolsMu mutex for thread safety
- Add comprehensive test suite with 4 test cases:
  * Mixed regular and task tools with proper sorting
  * Only task tools scenario
  * Empty list when no tools exist
  * Alphabetical ordering verification

Task tools identified by Execution.TaskSupport field in metadata.
Implement proper API for registering task-augmented tools following the
same pattern as regular tool registration (AddTool/AddTools).

- Add AddTaskTool() method for single task tool registration
- Add AddTaskTools() method for batch task tool registration
- Both methods implicitly register tool capabilities (listChanged=true)
- Send tools/list_changed notifications to initialized sessions
- Add comprehensive test suite TestMCPServer_AddTaskTool with 4 test cases
- Update existing tests to use new methods instead of direct map access

All tests pass with race detector, linter clean (0 issues).
Add TestTaskToolTracerBullet in server/task_tool_test.go with 6 end-to-end
test scenarios covering:
- Complete task tool flow with TaskSupportRequired
- TaskSupportOptional sync/async execution modes
- Task cancellation via context
- Error handling with proper task failure states
- Multiple concurrent task tools

Fix bug in handleToolCall where taskTools map was not checked during tool
lookup. Now checks both s.tools and s.taskTools, allowing tools registered
with AddTaskTool to be properly found and validated.

All tests pass with race detector, linter reports 0 issues.
- Add explicit checking for context.Canceled and context.DeadlineExceeded errors in executeTaskTool and executeRegularToolAsTask
- Mark tasks as 'cancelled' instead of 'failed' when handlers return context cancellation errors
- Handle race condition where handler detects cancellation before tasks/cancel is called
- Add comprehensive test for handler-detected context cancellation edge case
- Update existing test expectations to correctly expect TaskStatusCancelled

This ensures proper status differentiation between cancellation and failure, respecting Go's context patterns.
Implement comprehensive observability system for task-augmented tools:

- Add TaskMetrics struct with execution metadata (ID, tool name, status,
  timestamps, duration, session, error)
- Add TaskHooks with 5 lifecycle hook types: OnTaskCreated, OnTaskCompleted,
  OnTaskFailed, OnTaskCancelled, OnTaskStatusChanged
- Add WithTaskHooks() ServerOption for hook configuration
- Integrate hooks into task lifecycle (createTask, completeTask, cancelTask,
  executeTaskTool, executeRegularToolAsTask)
- Update taskEntry to track toolName and createdAt for metrics
- Add comprehensive test suite with 8 test cases covering all hook scenarios
- Update task_tool example to demonstrate hook usage with logging

This enables developers to monitor task execution, collect metrics, and
integrate with monitoring systems for async tool operations.

Resolves TAS-24
Add optional resource management for task-augmented tools through
WithMaxConcurrentTasks() ServerOption. This prevents resource exhaustion
by limiting the number of concurrent running tasks.

Changes:
- Add maxConcurrentTasks and activeTasks fields to MCPServer
- Add WithMaxConcurrentTasks(limit int) ServerOption
- Modify createTask() to check limit and return error when exceeded
- Update task completion/cancellation to decrement active task counter
- Add comprehensive test suite (8 test cases) with race detector
- Update examples and documentation

The limit only applies to non-terminal (working) tasks. Completed,
failed, or cancelled tasks don't count toward the limit. When not
configured or set to 0, no limit is enforced.

All tests pass with race detector. Linter clean.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 30, 2026

Walkthrough

Implements task-augmented tools and server-side Tasks capability: adds TaskSupport modes, task lifecycle (create/list/result/cancel), concurrency limits, task hooks/observability, TTL/expiration, related metadata helpers, hook signature updates, examples, and extensive tests across server and mcp packages.

Changes

Cohort / File(s) Summary
Core MCP types & utils
mcp/types.go, mcp/tasks.go, mcp/utils.go
Add Task.LastUpdatedAt, Task.GetName(), TaskParams.TTL, TaskResult result fields (Content/StructuredContent/IsError); introduce RelatedTaskMeta/WithRelatedTask and ModelImmediateResponse meta helpers; consolidate CreatedAt/LastUpdatedAt in NewTask; add ToInt64Ptr.
Tool API surface
mcp/tools.go, mcp/tools_test.go
Introduce TaskSupport enum and ToolExecution, add Tool.Execution and CallToolParams.Task, update Tool.MarshalJSON, add WithTaskSupport builder; tests for marshaling, options, and CallToolRequest task params.
Server core — task orchestration
server/server.go, server/task_hooks.go, server/task_hooks_test.go
Add ServerTaskTool/TaskToolHandlerFunc; taskEntry tracking, active/expired task counters, WithTaskHooks/WithMaxConcurrentTasks, AddTaskTool(s), async execution paths (executeTaskTool/executeRegularToolAsTask), create/complete/cancel/schedule cleanup, sendTaskStatusNotification, and full task hooks API; tests for hooks.
Server — task behavior & tests
server/task_test.go, server/task_tool_test.go, server/task_limit_test.go, server/task_response_format_test.go, server/server_test.go
Extensive tests for creation/completion/cancellation, polling/result retrieval, TTL/expiration, pagination, max-concurrency enforcement, hybrid sync/async routing, JSON-RPC response format, and many integration scenarios.
Hooks / request handling updates
server/hooks.go, server/request_handler.go, client/http_test.go, examples/everything/main.go, server/session_test.go, server/sse_test.go
Change OnAfterCallToolFunc and Hooks.afterCallTool to accept result any (broaden result type); update callers/tests to match new any result parameter.
Examples & docs
README.md, examples/task_tool/main.go, examples/...
Document Task-Augmented Tools, TaskSupport modes, async execution flow, concurrency limits; add examples/task_tool with sample task-required/optional/sync tools and observability hooks.
Tooling & small helpers
mcp/tasks_test.go, mcp/utils.go
Add unit tests for new metadata helpers and ModelImmediateResponse integration; add ToInt64Ptr helper.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

type: enhancement, area: sdk

Suggested reviewers

  • robert-jackson-glean
  • pottekkat
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: implementing task-augmented tools capability for the MCP server.
Description check ✅ Passed The description is comprehensive, covering the feature overview, objectives, implementation details, examples, and adhering to the template structure with all required sections properly filled.
Linked Issues check ✅ Passed The PR meets the objectives from issue #656: implements task-augmented tools, task lifecycle management, pagination, concurrency limits, observability hooks, and comprehensive tests for task behavior.
Out of Scope Changes check ✅ Passed All changes directly support task-augmented tools implementation. Updates to hooks.go and related files align with task integration requirements. README and example additions document the feature as intended.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 656-tasks-capability

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/server.go (1)

2157-2211: ⚠️ Potential issue | 🟠 Major

Hooks called while holding lock may cause blocking or deadlock.

completeTask holds s.tasksMu lock (via defer s.tasksMu.Unlock() at Line 2160) while:

  1. Calling s.sendTaskStatusNotification (Line 2190)
  2. Calling s.taskHooks.taskFailed/taskCompleted (Lines 2207-2209)

If hooks perform lengthy operations or try to access task state (which would require the same lock), this could cause:

  • Performance degradation (blocking other task operations)
  • Potential deadlock if hooks call back into task management

Consider releasing the lock before firing hooks:

🔒 Proposed fix to release lock before hooks
 func (s *MCPServer) completeTask(entry *taskEntry, result any, err error) {
 	s.tasksMu.Lock()
-	defer s.tasksMu.Unlock()
 
 	// Guard against double completion
 	if entry.completed {
+		s.tasksMu.Unlock()
 		return
 	}
 
 	completedAt := time.Now()
 	duration := completedAt.Sub(entry.createdAt)
 
 	if err != nil {
 		entry.task.Status = mcp.TaskStatusFailed
 		entry.task.StatusMessage = err.Error()
 		entry.resultErr = err
 	} else {
 		entry.task.Status = mcp.TaskStatusCompleted
 		entry.result = result
 	}
 
 	entry.task.LastUpdatedAt = completedAt.UTC().Format(time.RFC3339)
 	entry.completed = true
 	close(entry.done)
 	s.activeTasks--
 
+	// Copy data needed for hooks before releasing lock
+	taskCopy := entry.task
+	toolName := entry.toolName
+	createdAt := entry.createdAt
+	sessionID := entry.sessionID
+	s.tasksMu.Unlock()
+
 	// Send task status notification
-	s.sendTaskStatusNotification(entry.task)
+	s.sendTaskStatusNotification(taskCopy)
 
 	// Fire task hooks
 	if s.taskHooks != nil {
 		metrics := TaskMetrics{
-			TaskID:        entry.task.TaskId,
-			ToolName:      entry.toolName,
-			Status:        entry.task.Status,
-			StatusMessage: entry.task.StatusMessage,
-			CreatedAt:     entry.createdAt,
+			TaskID:        taskCopy.TaskId,
+			ToolName:      toolName,
+			Status:        taskCopy.Status,
+			StatusMessage: taskCopy.StatusMessage,
+			CreatedAt:     createdAt,
 			CompletedAt:   &completedAt,
 			Duration:      duration,
-			SessionID:     entry.sessionID,
+			SessionID:     sessionID,
 			Error:         err,
 		}
 		// ... rest of hook calls
 	}
 }
🤖 Fix all issues with AI agents
In `@examples/task_tool/README.md`:
- Line 112: The README's workflow phrase "pending → completed or failed" is
inconsistent with the code's TaskStatus constant named TaskStatusWorking; update
the README to use "working → completed or failed" (or alternatively rename the
constant to TaskStatusPending in code) so terminology matches the
implementation; locate references to TaskStatusWorking in the code and ensure
the README's status terms exactly match that constant name (or change the
constant and all its usages if you choose to rename it).
- Around line 17-26: Update the README checklist to mark TAS-4 through TAS-10 as
completed and remove the build constraint in main.go: change TAS-4
TaskToolHandlerFunc, TAS-5 ServerTaskTool, TAS-6 AddTaskTool, TAS-9
handleTaskAugmentedToolCall, and TAS-10 executeTaskTool entries to ✅ in
README.md and delete the "//go:build ignore" line from main.go so the program
can be built now that those symbols are implemented.

In `@README.md`:
- Around line 315-316: The sentence in README.md uses the noun form "timeout"
where the verb phrase "time out" is correct; update the sentence that currently
reads "would otherwise block or timeout" to "would otherwise block or time out"
so the verb form is used; ensure the change is made in the paragraph starting
"Task-augmented tools execute asynchronously and return results via polling" to
preserve grammar and spacing.

In `@server/server.go`:
- Around line 2037-2051: The concurrency check and increment in createTask has a
race because s.tasksMu is unlocked too early; hold the mutex for the full
critical section by calling s.tasksMu.Lock() and deferring s.tasksMu.Unlock()
(or explicitly unlocking only after you create/insert the task and increment
s.activeTasks) so the check against s.maxConcurrentTasks and the s.activeTasks++
happen atomically; update any corresponding decrement logic (where s.activeTasks
is decremented) to also use s.tasksMu to keep counts consistent.
- Around line 1545-1600: The code creates a ServerTaskTool wrapper (assigning to
toolToUse with a TaskToolHandlerFunc) but never uses it when hasTaskHandler is
false, causing dead code or wrong behavior; either remove the unused wrapper
block (the ServerTaskTool creation and any TaskToolHandlerFunc code) or change
the async call to use the wrapper by replacing the call to
executeRegularToolAsTask(ctx, entry, regularTool, request) with
executeRegularToolAsTask(ctx, entry, toolToUse, request) (or adjust
executeRegularToolAsTask signature to accept ServerTaskTool), ensuring
references to toolToUse, ServerTaskTool, TaskToolHandlerFunc, hasTaskHandler and
executeRegularToolAsTask/regularTool are updated consistently.
- Around line 1324-1347: Add validation to prevent registering the same tool
name in both s.tools and s.taskTools: in AddTools (and AddTaskTools /
AddTaskTool) check each incoming tool name against the opposite map (e.g., when
adding to s.tools, ensure the name is not already present in s.taskTools, and
vice versa) and return or report an error if a collision is detected instead of
allowing both to be registered; this ensures handleListTools (which currently
prefers s.tools over s.taskTools) won't silently hide task tools and keeps
registration atomic and deterministic.

In `@server/task_hooks_test.go`:
- Around line 19-35: The tests in server/task_hooks_test.go use time.Sleep to
wait for hook execution (e.g., after calling server.createTask in the
TaskHooks/AddOnTaskCreated tests), which causes flakiness; replace each fixed
sleep with a synchronization primitive such as a channel or sync.WaitGroup plus
a timeout context: have the hook callback signal completion (close a done
channel or call wg.Done), then the test should wait on that channel or wg with a
select that also listens on a time.After timeout and fail the test if the
timeout fires. Update all occurrences around TaskHooks/AddOnTaskCreated,
Create/Update/Delete hook tests (the hooks variable,
AddOnTaskCreated/AddOnTaskUpdated/AddOnTaskDeleted callbacks and where
server.createTask/server.updateTask/server.deleteTask is invoked) to use this
pattern rather than time.Sleep.

In `@server/task_tool_test.go`:
- Around line 59-180: The test has a data race: the async task handler writes to
handlerCalled and receivedData while the main goroutine reads them; protect
these shared variables (handlerCalled, receivedData) — e.g., change
handlerCalled to an int32 and use sync/atomic
(atomic.StoreInt32/atomic.LoadInt32) and protect receivedData with either a
sync.Mutex or atomic.Value, or use a channel to signal/transfer the string from
the handler; update the long_running tool handler in the test (the anonymous
func registered with server.AddTaskTool) and the assertion reads to use the
chosen synchronization primitives so reads/writes are race-free (also update
assertions to check atomic/mutex-protected values).
🧹 Nitpick comments (6)
server/task_test.go (2)

617-620: Consider using require.True for type assertions to avoid panics.

Lines 618-619 perform type assertions without checking the ok value. If these assertions fail, the test will panic rather than providing a clear failure message.

🔧 Suggested fix
-		resp := response.(mcp.JSONRPCResponse)
-		firstPage := resp.Result.(mcp.ListTasksResult)
+		resp, ok := response.(mcp.JSONRPCResponse)
+		require.True(t, ok, "Expected JSONRPCResponse")
+		firstPage, ok := resp.Result.(mcp.ListTasksResult)
+		require.True(t, ok, "Expected ListTasksResult")

756-757: Sleep duration for timestamp tests is necessary but slow.

The 1100ms sleep ensures RFC3339 timestamps (second precision) will differ. This is correct but makes the test suite slower. Consider adding a comment explaining why this duration is required, or using a test helper that can mock time if test speed becomes a concern.

server/task_hooks_test.go (1)

15-15: Add GoDoc comments for exported TestTaskHooks_ funcs.*

These exported test functions don’t have GoDoc comments starting with the identifier name. As per coding guidelines: All exported types and functions must have GoDoc comments starting with the identifier name; avoid inline comments unless necessary.

server/task_tool_test.go (1)

123-123: Reuse mcp.ToInt64Ptr instead of a local helper.

There’s already a shared helper (mcp.ToInt64Ptr) that can replace ptrInt64, avoiding duplication.

Possible simplification
-					TTL: ptrInt64(60000),
+					TTL: mcp.ToInt64Ptr(60000),
@@
-// ptrInt64 is a helper to get a pointer to an int64
-func ptrInt64(i int64) *int64 {
-	return &i
-}

Also applies to: 625-628

server/task_hooks.go (1)

55-78: Consider adding thread-safety documentation or synchronization.

The Add* methods modify slices without synchronization. While hooks are typically registered during server setup before any tasks are processed, concurrent registration during runtime would cause a data race.

Consider either:

  1. Adding a comment documenting that hooks must be registered before the server starts processing requests.
  2. Adding mutex protection if runtime registration is intended to be supported.
📝 Option 1: Add documentation
+// AddOnTaskCreated registers a hook for task creation events.
+// Note: Hooks should be registered during server initialization, before
+// the server starts processing requests, to avoid data races.
 func (h *TaskHooks) AddOnTaskCreated(hook OnTaskCreatedHookFunc) {
 	h.OnTaskCreated = append(h.OnTaskCreated, hook)
 }
server/server.go (1)

1641-1688: Significant code duplication in cancellation handling.

The cancellation handling logic in executeTaskTool (Lines 1641-1688) and executeRegularToolAsTask (Lines 1720-1767) is nearly identical (~50 lines). This violates DRY and makes maintenance error-prone.

Consider extracting a helper method:

♻️ Proposed refactor to extract common logic
// handleTaskContextCancellation handles the case where a task handler detects
// context cancellation before tasks/cancel was called.
func (s *MCPServer) handleTaskContextCancellation(ctx context.Context, entry *taskEntry, err error) {
	s.tasksMu.Lock()
	alreadyCancelled := entry.task.Status == mcp.TaskStatusCancelled
	s.tasksMu.Unlock()

	if alreadyCancelled {
		return
	}

	cancelledAt := time.Now()
	duration := cancelledAt.Sub(entry.createdAt)

	s.tasksMu.Lock()
	if !entry.completed {
		entry.task.Status = mcp.TaskStatusCancelled
		entry.task.StatusMessage = err.Error()
		entry.task.LastUpdatedAt = cancelledAt.UTC().Format(time.RFC3339)
		entry.completed = true
		close(entry.done)
		s.activeTasks--
		s.sendTaskStatusNotification(entry.task)

		if s.taskHooks != nil {
			metrics := TaskMetrics{
				TaskID:        entry.task.TaskId,
				ToolName:      entry.toolName,
				Status:        entry.task.Status,
				StatusMessage: entry.task.StatusMessage,
				CreatedAt:     entry.createdAt,
				CompletedAt:   &cancelledAt,
				Duration:      duration,
				SessionID:     entry.sessionID,
			}
			s.taskHooks.taskCancelled(ctx, metrics)
		}
	}
	s.tasksMu.Unlock()
}

Then both executeTaskTool and executeRegularToolAsTask can call:

if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
	s.handleTaskContextCancellation(ctx, entry, err)
	return
}

Also applies to: 1720-1767

…collision validation

- Fix race window in createTask() between checking concurrent task limit and inserting task
  - Restructure to build task entry first without lock
  - Use single critical section with defer for check + increment + insert
  - Prevents multiple goroutines from exceeding maxConcurrentTasks under high concurrency

- Add tool name collision validation
  - Prevent same tool name from being registered in both s.tools and s.taskTools
  - AddTools() now checks for collisions with s.taskTools before registration
  - AddTaskTools() now checks for collisions with s.tools before registration
  - Panic with clear error message on collision to fail fast during setup
  - Prevents silent shadowing where task tools become invisible in handleListTools

- Add comprehensive test coverage
  - Test concurrent task creation respects limit (existing test now passes reliably)
  - Add TestMCPServer_ToolNameCollisionValidation with 5 collision scenarios
  - All tests pass with race detector enabled
Shared variables handlerCalled and receivedData were accessed without
synchronization from both the async task handler goroutine and the main
test thread, causing data races detected by 'go test -race'. Added
sync.Mutex protection around all reads and writes to these variables.

All tests now pass with race detector enabled.
…iance

Task-augmented tool calls now return CreateTaskResult with task as a direct
field of result (result.task) instead of nested in _meta (result._meta.task),
matching the MCP Tasks Specification (2025-11-25).

Changes:
- Modified handleToolCall() to return 'any' to support both CallToolResult
  and CreateTaskResult
- Modified handleTaskAugmentedToolCall() to return *CreateTaskResult directly
- Updated code generation templates to support ResultIsAny flag for flexible
  return types
- Regenerated hooks.go and request_handler.go with updated templates
- Fixed all tests to use pointer type assertions and expect correct format
- Added comprehensive spec compliance tests

All tests pass with race detector enabled.

Resolves: #707 (TAS-1)
Updated doc comment spec version references for all task-related methods
from specification/draft to specification/2025-11-25 for spec compliance.

Changes:
- MethodTasksGet
- MethodTasksList
- MethodTasksResult
- MethodTasksCancel
- MethodNotificationTasksStatus

All 5 URLs now reference the published 2025-11-25 spec version.
- Add documentation to TaskStatusInputRequired explaining it's not yet implemented
- Remove unused wrapper code in handleTaskAugmentedToolCall that was never executed
- The wrapper was created but never used when hasTaskHandler=false

Related to #707
Replace all time.Sleep calls in task_hooks_test.go with channels and
sync.WaitGroup for reliable test synchronization. Update 7 test functions
to use select/timeout pattern for single-event tests and WaitGroup for
multi-event tests, eliminating race conditions and timing-dependent
flakiness.

Additionally:
- Add model-immediate-response support with helper functions and tests
- Improve task error differentiation (expired vs not found)
- Remove unused wrapper code in handleTaskAugmentedToolCall
- Add comprehensive test coverage for new functionality

All tests pass with race detector enabled.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@server/task_tool_test.go`:
- Around line 185-195: The test currently reads a single notification from
notifyChan and asserts it is mcp.TaskStatusCompleted, which can fail if an
earlier "working" status is received; change the read to loop (or drain) from
notifyChan until a notification with params["taskId"] == taskID and
params["status"] == mcp.TaskStatusCompleted is observed or a timeout elapses.
Use the existing notifyChan, taskID, mcp.TaskStatusCompleted and
statusNotification variables (and time.After for timeout) to repeatedly select
on notifyChan and the timeout, ignore non-matching/working notifications, and
only assert success when the completed status is seen; on timeout call t.Fatal
as before.
🧹 Nitpick comments (5)
server/task_response_format_test.go (1)

13-120: Prefer table-driven tests for these cases. The two similar tests can be merged into a single table-driven test to follow the repo testing guideline and reduce duplicated setup.

♻️ Possible refactor sketch
- func TestTaskAugmentedToolCall_ResponseFormat(t *testing.T) { ... }
- func TestTaskAugmentedToolCall_SpecCompliance(t *testing.T) { ... }
+ func TestTaskAugmentedToolCall_ResponseFormat(t *testing.T) {
+   tests := []struct {
+     name     string
+     request  string
+     validate func(t *testing.T, resp mcp.JSONRPCMessage)
+   }{
+     { name: "response format", request: `...`, validate: func(t *testing.T, resp mcp.JSONRPCMessage) { /* current assertions */ }},
+     { name: "spec compliance", request: `...`, validate: func(t *testing.T, resp mcp.JSONRPCMessage) { /* current assertions */ }},
+   }
+   for _, tt := range tests {
+     t.Run(tt.name, func(t *testing.T) {
+       // shared server/tool setup
+       response := server.HandleMessage(context.Background(), []byte(tt.request))
+       tt.validate(t, response)
+     })
+   }
+ }

As per coding guidelines: Write table-driven tests using a tests := []struct{ name, ... } pattern.

server/task_test.go (1)

811-812: Consider reducing test execution time.

The 1100ms sleeps (to ensure RFC3339 timestamps differ) make this test ~3.3 seconds slower. While necessary for second-precision timestamps, consider:

  1. Using a clock abstraction for faster unit tests
  2. Marking this as an integration test if speed becomes a concern

Current approach is correct but noted for future optimization.

server/server.go (3)

1608-1765: Significant code duplication between executeTaskTool and executeRegularToolAsTask.

These two functions share ~80% identical code, particularly the cancellation handling logic (Lines 1633-1676 vs 1712-1755). Consider extracting the common pattern into a helper function.

♻️ Suggested refactoring approach
// executeToolAsync is a helper that handles common task execution logic
func (s *MCPServer) executeToolAsync(
    ctx context.Context,
    entry *taskEntry,
    handler func(ctx context.Context) (any, error),
) {
    taskCtx, cancel := context.WithCancel(ctx)
    defer cancel()

    s.tasksMu.Lock()
    entry.cancelFunc = cancel
    s.tasksMu.Unlock()

    result, err := handler(taskCtx)

    if err != nil {
        if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
            s.handleTaskCancellation(ctx, entry, err)
            return
        }
        s.completeTask(entry, nil, err)
        return
    }
    s.completeTask(entry, result, nil)
}

Then executeTaskTool and executeRegularToolAsTask become thin wrappers that just call this helper with the appropriate handler.


2060-2071: Task hooks called while holding tasksMu lock may cause contention.

If taskCreated hooks are slow or perform I/O, they will block other task operations. Consider calling hooks outside the critical section:

♻️ Proposed improvement
 	// Increment active task counter and insert task atomically
 	s.activeTasks++
 	s.tasks[taskID] = entry
+	s.tasksMu.Unlock()
 
 	// Fire task created hook
 	if s.taskHooks != nil {
 		metrics := TaskMetrics{
 			TaskID:    taskID,
 			ToolName:  toolName,
 			Status:    task.Status,
 			CreatedAt: createdAt,
 			SessionID: getSessionID(ctx),
 		}
 		s.taskHooks.taskCreated(ctx, metrics)
 	}
 
-	s.tasksMu.Unlock()
+	s.tasksMu.Lock()
 
 	// Start TTL cleanup if specified

Alternatively, document that hook implementations must be non-blocking.


2277-2284: Consider batching tombstone cleanup to reduce goroutine count.

Each expired task spawns a dedicated goroutine for tombstone cleanup. With high task churn, this could lead to many concurrent goroutines. Consider:

  1. A single background cleanup goroutine that periodically sweeps expiredTasks
  2. Store expiration time and clean up during other operations

Current approach works but may not scale well with high task volumes.

- Update task tool test to drain notifyChan until completed status is
  received, preventing flaky failures when working status arrives first
- Fix grammar in README: 'timeout' -> 'time out' (verb form)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Tasks capability for MCP spec 2025-11-25

2 participants