Skip to content

Commit 4f790db

Browse files
committed
Rename TaskExecutionMode values to match updated spec
Update ToolExecution.taskSupport values per the latest MCP tasks spec: - "never" → "forbidden" - "always" → "required" - "optional" unchanged Add typed constants TASK_FORBIDDEN, TASK_OPTIONAL, TASK_REQUIRED for consistent usage throughout the codebase instead of hardcoded strings. Update all examples, tests, and documentation to use the new terminology.
1 parent 8460a5f commit 4f790db

File tree

12 files changed

+90
-73
lines changed

12 files changed

+90
-73
lines changed

examples/clients/simple-task-interactive-client/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ async def sampling_callback(context, params) -> CreateMessageResult:
4949
```python
5050
# Call a tool as a task (returns immediately with task reference)
5151
result = await session.experimental.call_tool_as_task("tool_name", {"arg": "value"})
52-
task_id = result.task.taskId
52+
task_id = result.taskSupport.taskId
5353

5454
# Get result - this delivers elicitation/sampling requests and blocks until complete
5555
final = await session.experimental.get_task_result(task_id, CallToolResult)

examples/servers/simple-task-interactive/mcp_simple_task_interactive/server.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,13 @@ async def list_tools() -> list[types.Tool]:
6969
name="confirm_delete",
7070
description="Asks for confirmation before deleting (demonstrates elicitation)",
7171
inputSchema={"type": "object", "properties": {"filename": {"type": "string"}}},
72-
execution=types.ToolExecution(task="always"),
72+
execution=types.ToolExecution(taskSupport=types.TASK_REQUIRED),
7373
),
7474
types.Tool(
7575
name="write_haiku",
7676
description="Asks LLM to write a haiku (demonstrates sampling)",
7777
inputSchema={"type": "object", "properties": {"topic": {"type": "string"}}},
78-
execution=types.ToolExecution(task="always"),
78+
execution=types.ToolExecution(taskSupport=types.TASK_REQUIRED),
7979
),
8080
]
8181

@@ -86,7 +86,7 @@ async def handle_call_tool(name: str, arguments: dict[str, Any]) -> list[types.T
8686
app = ctx.lifespan_context
8787

8888
# Validate task mode
89-
ctx.experimental.validate_task_mode("always")
89+
ctx.experimental.validate_task_mode(types.TASK_REQUIRED)
9090

9191
# Ensure handler is configured for response routing
9292
ensure_handler_configured(ctx.session, app)

examples/servers/simple-task/mcp_simple_task/server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ async def list_tools() -> list[types.Tool]:
4141
name="long_running_task",
4242
description="A task that takes a few seconds to complete with status updates",
4343
inputSchema={"type": "object", "properties": {}},
44-
execution=types.ToolExecution(task="always"),
44+
execution=types.ToolExecution(taskSupport=types.TASK_REQUIRED),
4545
)
4646
]
4747

@@ -52,7 +52,7 @@ async def handle_call_tool(name: str, arguments: dict[str, Any]) -> list[types.T
5252
app = ctx.lifespan_context
5353

5454
# Validate task mode - raises McpError(-32601) if client didn't use task augmentation
55-
ctx.experimental.validate_task_mode("always")
55+
ctx.experimental.validate_task_mode(types.TASK_REQUIRED)
5656

5757
# Create the task
5858
metadata = ctx.experimental.task_metadata

src/mcp/shared/context.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from mcp.shared.session import BaseSession
88
from mcp.types import (
99
METHOD_NOT_FOUND,
10+
TASK_FORBIDDEN,
11+
TASK_REQUIRED,
1012
ClientCapabilities,
1113
ErrorData,
1214
RequestId,
@@ -54,12 +56,13 @@ def validate_task_mode(
5456
Validate that the request is compatible with the tool's task execution mode.
5557
5658
Per MCP spec:
57-
- "always": Clients MUST invoke as task. Server returns -32601 if not.
58-
- "never" (or None): Clients MUST NOT invoke as task. Server returns -32601 if they do.
59+
- "required": Clients MUST invoke as task. Server returns -32601 if not.
60+
- "forbidden" (or None): Clients MUST NOT invoke as task. Server returns -32601 if they do.
5961
- "optional": Either is acceptable.
6062
6163
Args:
62-
tool_task_mode: The tool's execution.task value ("never", "optional", "always", or None)
64+
tool_task_mode: The tool's execution.taskSupport value
65+
("forbidden", "optional", "required", or None)
6366
raise_error: If True, raises McpError on validation failure. If False, returns ErrorData.
6467
6568
Returns:
@@ -69,16 +72,16 @@ def validate_task_mode(
6972
McpError: If invalid and raise_error=True
7073
"""
7174

72-
mode = tool_task_mode or "never"
75+
mode = tool_task_mode or TASK_FORBIDDEN
7376

7477
error: ErrorData | None = None
7578

76-
if mode == "always" and not self.is_task:
79+
if mode == TASK_REQUIRED and not self.is_task:
7780
error = ErrorData(
7881
code=METHOD_NOT_FOUND,
7982
message="This tool requires task-augmented invocation",
8083
)
81-
elif mode == "never" and self.is_task:
84+
elif mode == TASK_FORBIDDEN and self.is_task:
8285
error = ErrorData(
8386
code=METHOD_NOT_FOUND,
8487
message="This tool does not support task-augmented invocation",
@@ -107,24 +110,24 @@ def validate_for_tool(
107110
Returns:
108111
None if valid, ErrorData if invalid and raise_error=False
109112
"""
110-
mode = tool.execution.task if tool.execution else None
113+
mode = tool.execution.taskSupport if tool.execution else None
111114
return self.validate_task_mode(mode, raise_error=raise_error)
112115

113116
def can_use_tool(self, tool_task_mode: TaskExecutionMode | None) -> bool:
114117
"""
115118
Check if this client can use a tool with the given task mode.
116119
117120
Useful for filtering tool lists or providing warnings.
118-
Returns False if tool requires "always" but client doesn't support tasks.
121+
Returns False if tool requires "required" but client doesn't support tasks.
119122
120123
Args:
121-
tool_task_mode: The tool's execution.task value
124+
tool_task_mode: The tool's execution.taskSupport value
122125
123126
Returns:
124127
True if the client can use this tool, False otherwise
125128
"""
126-
mode = tool_task_mode or "never"
127-
if mode == "always" and not self.client_supports_tasks:
129+
mode = tool_task_mode or TASK_FORBIDDEN
130+
if mode == TASK_REQUIRED and not self.client_supports_tasks:
128131
return False
129132
return True
130133

src/mcp/types.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from collections.abc import Callable
22
from datetime import datetime
3-
from typing import Annotated, Any, Generic, Literal, TypeAlias, TypeVar
3+
from typing import Annotated, Any, Final, Generic, Literal, TypeAlias, TypeVar
44

55
from pydantic import BaseModel, ConfigDict, Field, FileUrl, RootModel
66
from pydantic.networks import AnyUrl, UrlConstraints
@@ -39,7 +39,11 @@
3939
Role = Literal["user", "assistant"]
4040
RequestId = Annotated[int, Field(strict=True)] | str
4141
AnyFunction: TypeAlias = Callable[..., Any]
42-
TaskExecutionMode = Literal["never", "optional", "always"]
42+
43+
TaskExecutionMode = Literal["forbidden", "optional", "required"]
44+
TASK_FORBIDDEN: Final[Literal["forbidden"]] = "forbidden"
45+
TASK_OPTIONAL: Final[Literal["optional"]] = "optional"
46+
TASK_REQUIRED: Final[Literal["required"]] = "required"
4347

4448

4549
class TaskMetadata(BaseModel):
@@ -1126,17 +1130,17 @@ class ToolExecution(BaseModel):
11261130

11271131
model_config = ConfigDict(extra="allow")
11281132

1129-
task: TaskExecutionMode | None = None
1133+
taskSupport: TaskExecutionMode | None = None
11301134
"""
11311135
Indicates whether this tool supports task-augmented execution.
11321136
This allows clients to handle long-running operations through polling
11331137
the task system.
11341138
1135-
- "never": Tool does not support task-augmented execution (default when absent)
1139+
- "forbidden": Tool does not support task-augmented execution (default when absent)
11361140
- "optional": Tool may support task-augmented execution
1137-
- "always": Tool requires task-augmented execution
1141+
- "required": Tool requires task-augmented execution
11381142
1139-
Default: "never"
1143+
Default: "forbidden"
11401144
"""
11411145

11421146

tests/experimental/tasks/server/test_elicitation_flow.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
)
3636
from mcp.shared.message import SessionMessage
3737
from mcp.types import (
38+
TASK_REQUIRED,
3839
CallToolRequest,
3940
CallToolRequestParams,
4041
CallToolResult,
@@ -94,7 +95,7 @@ async def list_tools():
9495
"type": "object",
9596
"properties": {"data": {"type": "string"}},
9697
},
97-
execution=ToolExecution(task="always"),
98+
execution=ToolExecution(taskSupport=TASK_REQUIRED),
9899
)
99100
]
100101

tests/experimental/tasks/server/test_integration.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from mcp.shared.message import SessionMessage
2626
from mcp.shared.session import RequestResponder
2727
from mcp.types import (
28+
TASK_REQUIRED,
2829
CallToolRequest,
2930
CallToolRequestParams,
3031
CallToolResult,
@@ -83,7 +84,7 @@ async def list_tools():
8384
"type": "object",
8485
"properties": {"input": {"type": "string"}},
8586
},
86-
execution=ToolExecution(task="always"),
87+
execution=ToolExecution(taskSupport=TASK_REQUIRED),
8788
)
8889
]
8990

tests/experimental/tasks/server/test_sampling_flow.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
)
3636
from mcp.shared.message import SessionMessage
3737
from mcp.types import (
38+
TASK_REQUIRED,
3839
CallToolRequest,
3940
CallToolRequestParams,
4041
CallToolResult,
@@ -95,7 +96,7 @@ async def list_tools():
9596
"type": "object",
9697
"properties": {"question": {"type": "string"}},
9798
},
98-
execution=ToolExecution(task="always"),
99+
execution=ToolExecution(taskSupport=TASK_REQUIRED),
99100
)
100101
]
101102

tests/experimental/tasks/server/test_server.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
from mcp.shared.message import SessionMessage
1515
from mcp.shared.session import RequestResponder
1616
from mcp.types import (
17+
TASK_FORBIDDEN,
18+
TASK_OPTIONAL,
19+
TASK_REQUIRED,
1720
CallToolRequest,
1821
CallToolRequestParams,
1922
CallToolResult,
@@ -226,19 +229,19 @@ async def list_tools():
226229
name="quick_tool",
227230
description="Fast tool",
228231
inputSchema={"type": "object", "properties": {}},
229-
execution=ToolExecution(task="never"),
232+
execution=ToolExecution(taskSupport=TASK_FORBIDDEN),
230233
),
231234
Tool(
232235
name="long_tool",
233236
description="Long running tool",
234237
inputSchema={"type": "object", "properties": {}},
235-
execution=ToolExecution(task="always"),
238+
execution=ToolExecution(taskSupport=TASK_REQUIRED),
236239
),
237240
Tool(
238241
name="flexible_tool",
239242
description="Can be either",
240243
inputSchema={"type": "object", "properties": {}},
241-
execution=ToolExecution(task="optional"),
244+
execution=ToolExecution(taskSupport=TASK_OPTIONAL),
242245
),
243246
]
244247

@@ -251,11 +254,11 @@ async def list_tools():
251254
tools = result.root.tools
252255

253256
assert tools[0].execution is not None
254-
assert tools[0].execution.task == "never"
257+
assert tools[0].execution.taskSupport == TASK_FORBIDDEN
255258
assert tools[1].execution is not None
256-
assert tools[1].execution.task == "always"
259+
assert tools[1].execution.taskSupport == TASK_REQUIRED
257260
assert tools[2].execution is not None
258-
assert tools[2].execution.task == "optional"
261+
assert tools[2].execution.taskSupport == TASK_OPTIONAL
259262

260263

261264
# --- Integration tests ---
@@ -274,7 +277,7 @@ async def list_tools():
274277
name="long_task",
275278
description="A long running task",
276279
inputSchema={"type": "object", "properties": {}},
277-
execution=ToolExecution(task="optional"),
280+
execution=ToolExecution(taskSupport="optional"),
278281
)
279282
]
280283

tests/experimental/tasks/test_interactive_example.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
)
4242
from mcp.shared.message import SessionMessage
4343
from mcp.types import (
44+
TASK_REQUIRED,
4445
CallToolResult,
4546
CreateMessageRequestParams,
4647
CreateMessageResult,
@@ -79,13 +80,13 @@ async def list_tools() -> list[Tool]:
7980
name="confirm_delete",
8081
description="Asks for confirmation before deleting (demonstrates elicitation)",
8182
inputSchema={"type": "object", "properties": {"filename": {"type": "string"}}},
82-
execution=ToolExecution(task="always"),
83+
execution=ToolExecution(taskSupport=TASK_REQUIRED),
8384
),
8485
Tool(
8586
name="write_haiku",
8687
description="Asks LLM to write a haiku (demonstrates sampling)",
8788
inputSchema={"type": "object", "properties": {"topic": {"type": "string"}}},
88-
execution=ToolExecution(task="always"),
89+
execution=ToolExecution(taskSupport=TASK_REQUIRED),
8990
),
9091
]
9192

@@ -95,7 +96,7 @@ async def handle_call_tool(name: str, arguments: dict[str, Any]) -> list[TextCon
9596
app = ctx.lifespan_context
9697

9798
# Validate task mode
98-
ctx.experimental.validate_task_mode("always")
99+
ctx.experimental.validate_task_mode(TASK_REQUIRED)
99100

100101
# Ensure handler is configured for response routing
101102
session_id = id(ctx.session)

0 commit comments

Comments
 (0)