fastmcp 2.14.4__py3-none-any.whl → 3.0.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. fastmcp/_vendor/__init__.py +1 -0
  2. fastmcp/_vendor/docket_di/README.md +7 -0
  3. fastmcp/_vendor/docket_di/__init__.py +163 -0
  4. fastmcp/cli/cli.py +112 -28
  5. fastmcp/cli/install/claude_code.py +1 -5
  6. fastmcp/cli/install/claude_desktop.py +1 -5
  7. fastmcp/cli/install/cursor.py +1 -5
  8. fastmcp/cli/install/gemini_cli.py +1 -5
  9. fastmcp/cli/install/mcp_json.py +1 -6
  10. fastmcp/cli/run.py +146 -5
  11. fastmcp/client/__init__.py +7 -9
  12. fastmcp/client/auth/oauth.py +18 -17
  13. fastmcp/client/client.py +100 -870
  14. fastmcp/client/elicitation.py +1 -1
  15. fastmcp/client/mixins/__init__.py +13 -0
  16. fastmcp/client/mixins/prompts.py +295 -0
  17. fastmcp/client/mixins/resources.py +325 -0
  18. fastmcp/client/mixins/task_management.py +157 -0
  19. fastmcp/client/mixins/tools.py +397 -0
  20. fastmcp/client/sampling/handlers/anthropic.py +2 -2
  21. fastmcp/client/sampling/handlers/openai.py +1 -1
  22. fastmcp/client/tasks.py +3 -3
  23. fastmcp/client/telemetry.py +47 -0
  24. fastmcp/client/transports/__init__.py +38 -0
  25. fastmcp/client/transports/base.py +82 -0
  26. fastmcp/client/transports/config.py +170 -0
  27. fastmcp/client/transports/http.py +145 -0
  28. fastmcp/client/transports/inference.py +154 -0
  29. fastmcp/client/transports/memory.py +90 -0
  30. fastmcp/client/transports/sse.py +89 -0
  31. fastmcp/client/transports/stdio.py +543 -0
  32. fastmcp/contrib/component_manager/README.md +4 -10
  33. fastmcp/contrib/component_manager/__init__.py +1 -2
  34. fastmcp/contrib/component_manager/component_manager.py +95 -160
  35. fastmcp/contrib/component_manager/example.py +1 -1
  36. fastmcp/contrib/mcp_mixin/example.py +4 -4
  37. fastmcp/contrib/mcp_mixin/mcp_mixin.py +11 -4
  38. fastmcp/decorators.py +41 -0
  39. fastmcp/dependencies.py +12 -1
  40. fastmcp/exceptions.py +4 -0
  41. fastmcp/experimental/server/openapi/__init__.py +18 -15
  42. fastmcp/mcp_config.py +13 -4
  43. fastmcp/prompts/__init__.py +6 -3
  44. fastmcp/prompts/function_prompt.py +465 -0
  45. fastmcp/prompts/prompt.py +321 -271
  46. fastmcp/resources/__init__.py +5 -3
  47. fastmcp/resources/function_resource.py +335 -0
  48. fastmcp/resources/resource.py +325 -115
  49. fastmcp/resources/template.py +215 -43
  50. fastmcp/resources/types.py +27 -12
  51. fastmcp/server/__init__.py +2 -2
  52. fastmcp/server/auth/__init__.py +14 -0
  53. fastmcp/server/auth/auth.py +30 -10
  54. fastmcp/server/auth/authorization.py +190 -0
  55. fastmcp/server/auth/oauth_proxy/__init__.py +14 -0
  56. fastmcp/server/auth/oauth_proxy/consent.py +361 -0
  57. fastmcp/server/auth/oauth_proxy/models.py +178 -0
  58. fastmcp/server/auth/{oauth_proxy.py → oauth_proxy/proxy.py} +24 -778
  59. fastmcp/server/auth/oauth_proxy/ui.py +277 -0
  60. fastmcp/server/auth/oidc_proxy.py +2 -2
  61. fastmcp/server/auth/providers/auth0.py +24 -94
  62. fastmcp/server/auth/providers/aws.py +26 -95
  63. fastmcp/server/auth/providers/azure.py +41 -129
  64. fastmcp/server/auth/providers/descope.py +18 -49
  65. fastmcp/server/auth/providers/discord.py +25 -86
  66. fastmcp/server/auth/providers/github.py +23 -87
  67. fastmcp/server/auth/providers/google.py +24 -87
  68. fastmcp/server/auth/providers/introspection.py +60 -79
  69. fastmcp/server/auth/providers/jwt.py +30 -67
  70. fastmcp/server/auth/providers/oci.py +47 -110
  71. fastmcp/server/auth/providers/scalekit.py +23 -61
  72. fastmcp/server/auth/providers/supabase.py +18 -47
  73. fastmcp/server/auth/providers/workos.py +34 -127
  74. fastmcp/server/context.py +372 -419
  75. fastmcp/server/dependencies.py +541 -251
  76. fastmcp/server/elicitation.py +20 -18
  77. fastmcp/server/event_store.py +3 -3
  78. fastmcp/server/http.py +16 -6
  79. fastmcp/server/lifespan.py +198 -0
  80. fastmcp/server/low_level.py +92 -2
  81. fastmcp/server/middleware/__init__.py +5 -1
  82. fastmcp/server/middleware/authorization.py +312 -0
  83. fastmcp/server/middleware/caching.py +101 -54
  84. fastmcp/server/middleware/middleware.py +6 -9
  85. fastmcp/server/middleware/ping.py +70 -0
  86. fastmcp/server/middleware/tool_injection.py +2 -2
  87. fastmcp/server/mixins/__init__.py +7 -0
  88. fastmcp/server/mixins/lifespan.py +217 -0
  89. fastmcp/server/mixins/mcp_operations.py +392 -0
  90. fastmcp/server/mixins/transport.py +342 -0
  91. fastmcp/server/openapi/__init__.py +41 -21
  92. fastmcp/server/openapi/components.py +16 -339
  93. fastmcp/server/openapi/routing.py +34 -118
  94. fastmcp/server/openapi/server.py +67 -392
  95. fastmcp/server/providers/__init__.py +71 -0
  96. fastmcp/server/providers/aggregate.py +261 -0
  97. fastmcp/server/providers/base.py +578 -0
  98. fastmcp/server/providers/fastmcp_provider.py +674 -0
  99. fastmcp/server/providers/filesystem.py +226 -0
  100. fastmcp/server/providers/filesystem_discovery.py +327 -0
  101. fastmcp/server/providers/local_provider/__init__.py +11 -0
  102. fastmcp/server/providers/local_provider/decorators/__init__.py +15 -0
  103. fastmcp/server/providers/local_provider/decorators/prompts.py +256 -0
  104. fastmcp/server/providers/local_provider/decorators/resources.py +240 -0
  105. fastmcp/server/providers/local_provider/decorators/tools.py +315 -0
  106. fastmcp/server/providers/local_provider/local_provider.py +465 -0
  107. fastmcp/server/providers/openapi/__init__.py +39 -0
  108. fastmcp/server/providers/openapi/components.py +332 -0
  109. fastmcp/server/providers/openapi/provider.py +405 -0
  110. fastmcp/server/providers/openapi/routing.py +109 -0
  111. fastmcp/server/providers/proxy.py +867 -0
  112. fastmcp/server/providers/skills/__init__.py +59 -0
  113. fastmcp/server/providers/skills/_common.py +101 -0
  114. fastmcp/server/providers/skills/claude_provider.py +44 -0
  115. fastmcp/server/providers/skills/directory_provider.py +153 -0
  116. fastmcp/server/providers/skills/skill_provider.py +432 -0
  117. fastmcp/server/providers/skills/vendor_providers.py +142 -0
  118. fastmcp/server/providers/wrapped_provider.py +140 -0
  119. fastmcp/server/proxy.py +34 -700
  120. fastmcp/server/sampling/run.py +341 -2
  121. fastmcp/server/sampling/sampling_tool.py +4 -3
  122. fastmcp/server/server.py +1214 -2171
  123. fastmcp/server/tasks/__init__.py +2 -1
  124. fastmcp/server/tasks/capabilities.py +13 -1
  125. fastmcp/server/tasks/config.py +66 -3
  126. fastmcp/server/tasks/handlers.py +65 -273
  127. fastmcp/server/tasks/keys.py +4 -6
  128. fastmcp/server/tasks/requests.py +474 -0
  129. fastmcp/server/tasks/routing.py +76 -0
  130. fastmcp/server/tasks/subscriptions.py +20 -11
  131. fastmcp/server/telemetry.py +131 -0
  132. fastmcp/server/transforms/__init__.py +244 -0
  133. fastmcp/server/transforms/namespace.py +193 -0
  134. fastmcp/server/transforms/prompts_as_tools.py +175 -0
  135. fastmcp/server/transforms/resources_as_tools.py +190 -0
  136. fastmcp/server/transforms/tool_transform.py +96 -0
  137. fastmcp/server/transforms/version_filter.py +124 -0
  138. fastmcp/server/transforms/visibility.py +526 -0
  139. fastmcp/settings.py +34 -96
  140. fastmcp/telemetry.py +122 -0
  141. fastmcp/tools/__init__.py +10 -3
  142. fastmcp/tools/function_parsing.py +201 -0
  143. fastmcp/tools/function_tool.py +467 -0
  144. fastmcp/tools/tool.py +215 -362
  145. fastmcp/tools/tool_transform.py +38 -21
  146. fastmcp/utilities/async_utils.py +69 -0
  147. fastmcp/utilities/components.py +152 -91
  148. fastmcp/utilities/inspect.py +8 -20
  149. fastmcp/utilities/json_schema.py +12 -5
  150. fastmcp/utilities/json_schema_type.py +17 -15
  151. fastmcp/utilities/lifespan.py +56 -0
  152. fastmcp/utilities/logging.py +12 -4
  153. fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +3 -3
  154. fastmcp/utilities/openapi/parser.py +3 -3
  155. fastmcp/utilities/pagination.py +80 -0
  156. fastmcp/utilities/skills.py +253 -0
  157. fastmcp/utilities/tests.py +0 -16
  158. fastmcp/utilities/timeout.py +47 -0
  159. fastmcp/utilities/types.py +1 -1
  160. fastmcp/utilities/versions.py +285 -0
  161. {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/METADATA +8 -5
  162. fastmcp-3.0.0b1.dist-info/RECORD +228 -0
  163. fastmcp/client/transports.py +0 -1170
  164. fastmcp/contrib/component_manager/component_service.py +0 -209
  165. fastmcp/prompts/prompt_manager.py +0 -117
  166. fastmcp/resources/resource_manager.py +0 -338
  167. fastmcp/server/tasks/converters.py +0 -206
  168. fastmcp/server/tasks/protocol.py +0 -359
  169. fastmcp/tools/tool_manager.py +0 -170
  170. fastmcp/utilities/mcp_config.py +0 -56
  171. fastmcp-2.14.4.dist-info/RECORD +0 -161
  172. /fastmcp/server/{openapi → providers/openapi}/README.md +0 -0
  173. {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/WHEEL +0 -0
  174. {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/entry_points.txt +0 -0
  175. {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,157 @@
1
+ """Task management methods for FastMCP Client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ import mcp.types
8
+ from mcp import McpError
9
+
10
+ if TYPE_CHECKING:
11
+ from fastmcp.client.client import Client
12
+ from mcp.types import (
13
+ CancelTaskRequest,
14
+ CancelTaskRequestParams,
15
+ GetTaskPayloadRequest,
16
+ GetTaskPayloadRequestParams,
17
+ GetTaskPayloadResult,
18
+ GetTaskRequest,
19
+ GetTaskRequestParams,
20
+ GetTaskResult,
21
+ ListTasksRequest,
22
+ PaginatedRequestParams,
23
+ )
24
+
25
+ from fastmcp.utilities.logging import get_logger
26
+
27
+ logger = get_logger(__name__)
28
+
29
+
30
+ class ClientTaskManagementMixin:
31
+ """Mixin providing task management methods for Client."""
32
+
33
+ async def get_task_status(self: Client, task_id: str) -> GetTaskResult:
34
+ """Query the status of a background task.
35
+
36
+ Sends a 'tasks/get' MCP protocol request over the existing transport.
37
+
38
+ Args:
39
+ task_id: The task ID returned from call_tool_as_task
40
+
41
+ Returns:
42
+ GetTaskResult: Status information including taskId, status, pollInterval, etc.
43
+
44
+ Raises:
45
+ RuntimeError: If client not connected
46
+ McpError: If the request results in a TimeoutError | JSONRPCError
47
+ """
48
+ request = GetTaskRequest(params=GetTaskRequestParams(taskId=task_id))
49
+ return await self._await_with_session_monitoring(
50
+ self.session.send_request(
51
+ request=request, # type: ignore[arg-type]
52
+ result_type=GetTaskResult,
53
+ )
54
+ )
55
+
56
+ async def get_task_result(self: Client, task_id: str) -> Any:
57
+ """Retrieve the raw result of a completed background task.
58
+
59
+ Sends a 'tasks/result' MCP protocol request over the existing transport.
60
+ Returns the raw result - callers should parse it appropriately.
61
+
62
+ Args:
63
+ task_id: The task ID returned from call_tool_as_task
64
+
65
+ Returns:
66
+ Any: The raw result (could be tool, prompt, or resource result)
67
+
68
+ Raises:
69
+ RuntimeError: If client not connected, task not found, or task failed
70
+ McpError: If the request results in a TimeoutError | JSONRPCError
71
+ """
72
+ request = GetTaskPayloadRequest(
73
+ params=GetTaskPayloadRequestParams(taskId=task_id)
74
+ )
75
+ # Return raw result - Task classes handle type-specific parsing
76
+ result = await self._await_with_session_monitoring(
77
+ self.session.send_request(
78
+ request=request, # type: ignore[arg-type]
79
+ result_type=GetTaskPayloadResult,
80
+ )
81
+ )
82
+ # Return as dict for compatibility with Task class parsing
83
+ return result.model_dump(exclude_none=True, by_alias=True)
84
+
85
+ async def list_tasks(
86
+ self: Client,
87
+ cursor: str | None = None,
88
+ limit: int = 50,
89
+ ) -> dict[str, Any]:
90
+ """List background tasks.
91
+
92
+ Sends a 'tasks/list' MCP protocol request to the server. If the server
93
+ returns an empty list (indicating client-side tracking), falls back to
94
+ querying status for locally tracked task IDs.
95
+
96
+ Args:
97
+ cursor: Optional pagination cursor
98
+ limit: Maximum number of tasks to return (default 50)
99
+
100
+ Returns:
101
+ dict: Response with structure:
102
+ - tasks: List of task status dicts with taskId, status, etc.
103
+ - nextCursor: Optional cursor for next page
104
+
105
+ Raises:
106
+ RuntimeError: If client not connected
107
+ McpError: If the request results in a TimeoutError | JSONRPCError
108
+ """
109
+ # Send protocol request
110
+ params = PaginatedRequestParams(cursor=cursor, limit=limit) # type: ignore[call-arg] # Optional field in MCP SDK
111
+ request = ListTasksRequest(params=params)
112
+ server_response = await self._await_with_session_monitoring(
113
+ self.session.send_request(
114
+ request=request, # type: ignore[invalid-argument-type]
115
+ result_type=mcp.types.ListTasksResult,
116
+ )
117
+ )
118
+
119
+ # If server returned tasks, use those
120
+ if server_response.tasks:
121
+ return server_response.model_dump(by_alias=True)
122
+
123
+ # Server returned empty - fall back to client-side tracking
124
+ tasks = []
125
+ for task_id in list(self._submitted_task_ids)[:limit]:
126
+ try:
127
+ status = await self.get_task_status(task_id)
128
+ tasks.append(status.model_dump(by_alias=True))
129
+ except McpError:
130
+ # Task may have expired or been deleted, skip it
131
+ continue
132
+
133
+ return {"tasks": tasks, "nextCursor": None}
134
+
135
+ async def cancel_task(self: Client, task_id: str) -> mcp.types.CancelTaskResult:
136
+ """Cancel a task, transitioning it to cancelled state.
137
+
138
+ Sends a 'tasks/cancel' MCP protocol request. Task will halt execution
139
+ and transition to cancelled state.
140
+
141
+ Args:
142
+ task_id: The task ID to cancel
143
+
144
+ Returns:
145
+ CancelTaskResult: The task status showing cancelled state
146
+
147
+ Raises:
148
+ RuntimeError: If task doesn't exist
149
+ McpError: If the request results in a TimeoutError | JSONRPCError
150
+ """
151
+ request = CancelTaskRequest(params=CancelTaskRequestParams(taskId=task_id))
152
+ return await self._await_with_session_monitoring(
153
+ self.session.send_request(
154
+ request=request, # type: ignore[invalid-argument-type]
155
+ result_type=mcp.types.CancelTaskResult,
156
+ )
157
+ )
@@ -0,0 +1,397 @@
1
+ """Tool-related methods for FastMCP Client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import uuid
6
+ import weakref
7
+ from typing import TYPE_CHECKING, Any, Literal, overload
8
+
9
+ import mcp.types
10
+ from pydantic import RootModel
11
+
12
+ if TYPE_CHECKING:
13
+ import datetime
14
+
15
+ from fastmcp.client.client import CallToolResult, Client
16
+ from fastmcp.client.progress import ProgressHandler
17
+ from fastmcp.client.tasks import ToolTask
18
+ from fastmcp.client.telemetry import client_span
19
+ from fastmcp.exceptions import ToolError
20
+ from fastmcp.telemetry import inject_trace_context
21
+ from fastmcp.utilities.json_schema_type import json_schema_to_type
22
+ from fastmcp.utilities.logging import get_logger
23
+ from fastmcp.utilities.timeout import normalize_timeout_to_timedelta
24
+ from fastmcp.utilities.types import get_cached_typeadapter
25
+
26
+ logger = get_logger(__name__)
27
+
28
+ # Type alias for task response union (SEP-1686 graceful degradation)
29
+ ToolTaskResponseUnion = RootModel[mcp.types.CreateTaskResult | mcp.types.CallToolResult]
30
+
31
+
32
+ class ClientToolsMixin:
33
+ """Mixin providing tool-related methods for Client."""
34
+
35
+ # --- Tools ---
36
+
37
+ async def list_tools_mcp(
38
+ self: Client, *, cursor: str | None = None
39
+ ) -> mcp.types.ListToolsResult:
40
+ """Send a tools/list request and return the complete MCP protocol result.
41
+
42
+ Args:
43
+ cursor: Optional pagination cursor from a previous request's nextCursor.
44
+
45
+ Returns:
46
+ mcp.types.ListToolsResult: The complete response object from the protocol,
47
+ containing the list of tools and any additional metadata.
48
+
49
+ Raises:
50
+ RuntimeError: If called while the client is not connected.
51
+ McpError: If the request results in a TimeoutError | JSONRPCError
52
+ """
53
+ logger.debug(f"[{self.name}] called list_tools")
54
+
55
+ result = await self._await_with_session_monitoring(
56
+ self.session.list_tools(cursor=cursor)
57
+ )
58
+ return result
59
+
60
+ async def list_tools(self: Client) -> list[mcp.types.Tool]:
61
+ """Retrieve all tools available on the server.
62
+
63
+ This method automatically fetches all pages if the server paginates results,
64
+ returning the complete list. For manual pagination control (e.g., to handle
65
+ large result sets incrementally), use list_tools_mcp() with the cursor parameter.
66
+
67
+ Returns:
68
+ list[mcp.types.Tool]: A list of all Tool objects.
69
+
70
+ Raises:
71
+ RuntimeError: If called while the client is not connected.
72
+ McpError: If the request results in a TimeoutError | JSONRPCError
73
+ """
74
+ all_tools: list[mcp.types.Tool] = []
75
+ cursor: str | None = None
76
+
77
+ while True:
78
+ result = await self.list_tools_mcp(cursor=cursor)
79
+ all_tools.extend(result.tools)
80
+ if result.nextCursor is None:
81
+ break
82
+ cursor = result.nextCursor
83
+
84
+ return all_tools
85
+
86
+ # --- Call Tool ---
87
+
88
+ async def call_tool_mcp(
89
+ self: Client,
90
+ name: str,
91
+ arguments: dict[str, Any],
92
+ progress_handler: ProgressHandler | None = None,
93
+ timeout: datetime.timedelta | float | int | None = None,
94
+ meta: dict[str, Any] | None = None,
95
+ ) -> mcp.types.CallToolResult:
96
+ """Send a tools/call request and return the complete MCP protocol result.
97
+
98
+ This method returns the raw CallToolResult object, which includes an isError flag
99
+ and other metadata. It does not raise an exception if the tool call results in an error.
100
+
101
+ Args:
102
+ name (str): The name of the tool to call.
103
+ arguments (dict[str, Any]): Arguments to pass to the tool.
104
+ timeout (datetime.timedelta | float | int | None, optional): The timeout for the tool call. Defaults to None.
105
+ progress_handler (ProgressHandler | None, optional): The progress handler to use for the tool call. Defaults to None.
106
+ meta (dict[str, Any] | None, optional): Additional metadata to include with the request.
107
+ This is useful for passing contextual information (like user IDs, trace IDs, or preferences)
108
+ that shouldn't be tool arguments but may influence server-side processing. The server
109
+ can access this via `context.request_context.meta`. Defaults to None.
110
+
111
+ Returns:
112
+ mcp.types.CallToolResult: The complete response object from the protocol,
113
+ containing the tool result and any additional metadata.
114
+
115
+ Raises:
116
+ RuntimeError: If called while the client is not connected.
117
+ McpError: If the tool call requests results in a TimeoutError | JSONRPCError
118
+ """
119
+ with client_span(
120
+ f"tools/call {name}",
121
+ "tools/call",
122
+ name,
123
+ session_id=self.transport.get_session_id(),
124
+ ):
125
+ logger.debug(f"[{self.name}] called call_tool: {name}")
126
+
127
+ # Inject trace context into meta for propagation to server
128
+ propagated_meta = inject_trace_context(meta)
129
+
130
+ result = await self._await_with_session_monitoring(
131
+ self.session.call_tool(
132
+ name=name,
133
+ arguments=arguments,
134
+ read_timeout_seconds=normalize_timeout_to_timedelta(timeout),
135
+ progress_callback=progress_handler or self._progress_handler,
136
+ meta=propagated_meta if propagated_meta else None,
137
+ )
138
+ )
139
+ return result
140
+
141
+ async def _parse_call_tool_result(
142
+ self: Client,
143
+ name: str,
144
+ result: mcp.types.CallToolResult,
145
+ raise_on_error: bool = False,
146
+ ) -> CallToolResult:
147
+ """Parse an mcp.types.CallToolResult into our CallToolResult dataclass.
148
+
149
+ Args:
150
+ name: Tool name (for schema lookup)
151
+ result: Raw MCP protocol result
152
+ raise_on_error: Whether to raise ToolError on errors
153
+
154
+ Returns:
155
+ CallToolResult: Parsed result with structured data
156
+ """
157
+
158
+ return await _parse_call_tool_result(
159
+ name=name,
160
+ result=result,
161
+ tool_output_schemas=self.session._tool_output_schemas,
162
+ list_tools_fn=self.session.list_tools,
163
+ client_name=self.name,
164
+ raise_on_error=raise_on_error,
165
+ )
166
+
167
+ @overload
168
+ async def call_tool(
169
+ self: Client,
170
+ name: str,
171
+ arguments: dict[str, Any] | None = None,
172
+ *,
173
+ version: str | None = None,
174
+ timeout: datetime.timedelta | float | int | None = None,
175
+ progress_handler: ProgressHandler | None = None,
176
+ raise_on_error: bool = True,
177
+ meta: dict[str, Any] | None = None,
178
+ task: Literal[False] = False,
179
+ ) -> CallToolResult: ...
180
+
181
+ @overload
182
+ async def call_tool(
183
+ self: Client,
184
+ name: str,
185
+ arguments: dict[str, Any] | None = None,
186
+ *,
187
+ version: str | None = None,
188
+ timeout: datetime.timedelta | float | int | None = None,
189
+ progress_handler: ProgressHandler | None = None,
190
+ raise_on_error: bool = True,
191
+ meta: dict[str, Any] | None = None,
192
+ task: Literal[True],
193
+ task_id: str | None = None,
194
+ ttl: int = 60000,
195
+ ) -> ToolTask: ...
196
+
197
+ async def call_tool(
198
+ self: Client,
199
+ name: str,
200
+ arguments: dict[str, Any] | None = None,
201
+ *,
202
+ version: str | None = None,
203
+ timeout: datetime.timedelta | float | int | None = None,
204
+ progress_handler: ProgressHandler | None = None,
205
+ raise_on_error: bool = True,
206
+ meta: dict[str, Any] | None = None,
207
+ task: bool = False,
208
+ task_id: str | None = None,
209
+ ttl: int = 60000,
210
+ ) -> CallToolResult | ToolTask:
211
+ """Call a tool on the server.
212
+
213
+ Unlike call_tool_mcp, this method raises a ToolError if the tool call results in an error.
214
+
215
+ Args:
216
+ name (str): The name of the tool to call.
217
+ arguments (dict[str, Any] | None, optional): Arguments to pass to the tool. Defaults to None.
218
+ version (str | None, optional): Specific tool version to call. If None, calls highest version.
219
+ timeout (datetime.timedelta | float | int | None, optional): The timeout for the tool call. Defaults to None.
220
+ progress_handler (ProgressHandler | None, optional): The progress handler to use for the tool call. Defaults to None.
221
+ raise_on_error (bool, optional): Whether to raise an exception if the tool call results in an error. Defaults to True.
222
+ meta (dict[str, Any] | None, optional): Additional metadata to include with the request.
223
+ This is useful for passing contextual information (like user IDs, trace IDs, or preferences)
224
+ that shouldn't be tool arguments but may influence server-side processing. The server
225
+ can access this via `context.request_context.meta`. Defaults to None.
226
+ task (bool): If True, execute as background task (SEP-1686). Defaults to False.
227
+ task_id (str | None): Optional client-provided task ID (auto-generated if not provided).
228
+ ttl (int): Time to keep results available in milliseconds (default 60s).
229
+
230
+ Returns:
231
+ CallToolResult | ToolTask: The content returned by the tool if task=False,
232
+ or a ToolTask object if task=True. If the tool returns structured
233
+ outputs, they are returned as a dataclass (if an output schema
234
+ is available) or a dictionary; otherwise, a list of content
235
+ blocks is returned. Note: to receive both structured and
236
+ unstructured outputs, use call_tool_mcp instead and access the
237
+ raw result object.
238
+
239
+ Raises:
240
+ ToolError: If the tool call results in an error.
241
+ McpError: If the tool call request results in a TimeoutError | JSONRPCError
242
+ RuntimeError: If called while the client is not connected.
243
+ """
244
+ # Merge version into request-level meta (not arguments)
245
+ request_meta = dict(meta) if meta else {}
246
+ if version is not None:
247
+ request_meta["fastmcp"] = {
248
+ **request_meta.get("fastmcp", {}),
249
+ "version": version,
250
+ }
251
+
252
+ if task:
253
+ return await self._call_tool_as_task(
254
+ name, arguments, task_id, ttl, meta=request_meta or None
255
+ )
256
+
257
+ result = await self.call_tool_mcp(
258
+ name=name,
259
+ arguments=arguments or {},
260
+ timeout=timeout,
261
+ progress_handler=progress_handler,
262
+ meta=request_meta or None,
263
+ )
264
+ return await self._parse_call_tool_result(
265
+ name, result, raise_on_error=raise_on_error
266
+ )
267
+
268
+ async def _call_tool_as_task(
269
+ self: Client,
270
+ name: str,
271
+ arguments: dict[str, Any] | None = None,
272
+ task_id: str | None = None,
273
+ ttl: int = 60000,
274
+ meta: dict[str, Any] | None = None,
275
+ ) -> ToolTask:
276
+ """Call a tool for background execution (SEP-1686).
277
+
278
+ Returns a ToolTask object that handles both background and immediate execution.
279
+ If the server accepts background execution, ToolTask will poll for results.
280
+ If the server declines (graceful degradation), ToolTask wraps the immediate result.
281
+
282
+ Args:
283
+ name: Tool name to call
284
+ arguments: Tool arguments
285
+ task_id: Optional client-provided task ID (ignored, for backward compatibility)
286
+ ttl: Time to keep results available in milliseconds (default 60s)
287
+ meta: Optional request metadata (e.g., version info)
288
+
289
+ Returns:
290
+ ToolTask: Future-like object for accessing task status and results
291
+ """
292
+ # Per SEP-1686 final spec: client sends only ttl, server generates taskId
293
+ # Inject trace context into meta for propagation to server
294
+ propagated_meta = inject_trace_context(meta)
295
+
296
+ # Build request with task metadata
297
+ request = mcp.types.CallToolRequest(
298
+ params=mcp.types.CallToolRequestParams(
299
+ name=name,
300
+ arguments=arguments or {},
301
+ task=mcp.types.TaskMetadata(ttl=ttl),
302
+ _meta=propagated_meta, # type: ignore[unknown-argument] # pydantic alias
303
+ )
304
+ )
305
+
306
+ # Server returns CreateTaskResult (task accepted) or CallToolResult (graceful degradation)
307
+ # Use RootModel with Union to handle both response types (SDK calls model_validate)
308
+ wrapped_result = await self._await_with_session_monitoring(
309
+ self.session.send_request(
310
+ request=request, # type: ignore[arg-type]
311
+ result_type=ToolTaskResponseUnion,
312
+ )
313
+ )
314
+ raw_result = wrapped_result.root
315
+
316
+ if isinstance(raw_result, mcp.types.CreateTaskResult):
317
+ # Task was accepted - extract task info from CreateTaskResult
318
+ server_task_id = raw_result.task.taskId
319
+ self._submitted_task_ids.add(server_task_id)
320
+
321
+ task_obj = ToolTask(
322
+ self, server_task_id, tool_name=name, immediate_result=None
323
+ )
324
+ self._task_registry[server_task_id] = weakref.ref(task_obj)
325
+ return task_obj
326
+ else:
327
+ # Graceful degradation - server returned CallToolResult
328
+ parsed_result = await self._parse_call_tool_result(name, raw_result)
329
+ synthetic_task_id = task_id or str(uuid.uuid4())
330
+ return ToolTask(
331
+ self,
332
+ synthetic_task_id,
333
+ tool_name=name,
334
+ immediate_result=parsed_result,
335
+ )
336
+
337
+
338
+ async def _parse_call_tool_result(
339
+ name: str,
340
+ result: mcp.types.CallToolResult,
341
+ tool_output_schemas: dict[str, dict[str, Any] | None],
342
+ list_tools_fn: Any, # Callable[[], Awaitable[None]]
343
+ client_name: str | None = None,
344
+ raise_on_error: bool = False,
345
+ ) -> CallToolResult:
346
+ """Parse an mcp.types.CallToolResult into our CallToolResult dataclass.
347
+
348
+ Args:
349
+ name: Tool name (for schema lookup)
350
+ result: Raw MCP protocol result
351
+ tool_output_schemas: Dictionary mapping tool names to their output schemas
352
+ list_tools_fn: Async function to refresh tool schemas if needed
353
+ client_name: Optional client name for logging
354
+ raise_on_error: Whether to raise ToolError on errors
355
+
356
+ Returns:
357
+ CallToolResult: Parsed result with structured data
358
+ """
359
+ from typing import cast
360
+
361
+ from fastmcp.client.client import CallToolResult
362
+
363
+ data = None
364
+ if result.isError and raise_on_error:
365
+ msg = cast(mcp.types.TextContent, result.content[0]).text
366
+ raise ToolError(msg)
367
+ elif result.structuredContent:
368
+ try:
369
+ if name not in tool_output_schemas:
370
+ await list_tools_fn()
371
+ if name in tool_output_schemas:
372
+ output_schema = tool_output_schemas.get(name)
373
+ if output_schema:
374
+ if output_schema.get("x-fastmcp-wrap-result"):
375
+ output_schema = output_schema.get("properties", {}).get(
376
+ "result"
377
+ )
378
+ structured_content = result.structuredContent.get("result")
379
+ else:
380
+ structured_content = result.structuredContent
381
+ output_type = json_schema_to_type(output_schema)
382
+ type_adapter = get_cached_typeadapter(output_type)
383
+ data = type_adapter.validate_python(structured_content)
384
+ else:
385
+ data = result.structuredContent
386
+ except Exception as e:
387
+ logger.error(
388
+ f"[{client_name or 'client'}] Error parsing structured content: {e}"
389
+ )
390
+
391
+ return CallToolResult(
392
+ content=result.content,
393
+ structured_content=result.structuredContent,
394
+ meta=result.meta,
395
+ data=data,
396
+ is_error=result.isError,
397
+ )
@@ -198,7 +198,7 @@ class AnthropicSamplingHandler:
198
198
  anthropic_messages.append(
199
199
  MessageParam(
200
200
  role=message.role,
201
- content=content_blocks, # type: ignore[arg-type]
201
+ content=content_blocks,
202
202
  )
203
203
  )
204
204
  continue
@@ -310,7 +310,7 @@ class AnthropicSamplingHandler:
310
310
  ToolParam(
311
311
  name=tool.name,
312
312
  description=tool.description or "",
313
- input_schema=input_schema, # type: ignore[arg-type]
313
+ input_schema=input_schema,
314
314
  )
315
315
  )
316
316
  return anthropic_tools
@@ -368,7 +368,7 @@ class OpenAISamplingHandler:
368
368
  # Skip non-function tool calls
369
369
  if not hasattr(tool_call, "function"):
370
370
  continue
371
- func = tool_call.function # type: ignore[union-attr]
371
+ func = tool_call.function
372
372
  # Parse the arguments JSON string
373
373
  try:
374
374
  arguments = json.loads(func.arguments) # type: ignore[union-attr]
fastmcp/client/tasks.py CHANGED
@@ -378,15 +378,15 @@ class ToolTask(Task["CallToolResult"]):
378
378
  ):
379
379
  mcp_result = mcp.types.CallToolResult(
380
380
  content=raw_result.content,
381
- structuredContent=raw_result.structured_content, # type: ignore[arg-type]
382
- _meta=raw_result.meta,
381
+ structuredContent=raw_result.structured_content,
382
+ _meta=raw_result.meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
383
383
  )
384
384
  result = await self._client._parse_call_tool_result(
385
385
  self._tool_name, mcp_result, raise_on_error=True
386
386
  )
387
387
  else:
388
388
  # Unknown type - just return it
389
- result = raw_result # type: ignore[assignment]
389
+ result = raw_result
390
390
 
391
391
  # Cache before returning
392
392
  self._cached_result = result
@@ -0,0 +1,47 @@
1
+ """Client-side telemetry helpers."""
2
+
3
+ from collections.abc import Generator
4
+ from contextlib import contextmanager
5
+
6
+ from opentelemetry.trace import Span, SpanKind, Status, StatusCode
7
+
8
+ from fastmcp.telemetry import get_tracer
9
+
10
+
11
+ @contextmanager
12
+ def client_span(
13
+ name: str,
14
+ method: str,
15
+ component_key: str,
16
+ session_id: str | None = None,
17
+ resource_uri: str | None = None,
18
+ ) -> Generator[Span, None, None]:
19
+ """Create a CLIENT span with standard MCP attributes.
20
+
21
+ Automatically records any exception on the span and sets error status.
22
+ """
23
+ tracer = get_tracer()
24
+ with tracer.start_as_current_span(name, kind=SpanKind.CLIENT) as span:
25
+ attrs: dict[str, str] = {
26
+ # RPC semantic conventions
27
+ "rpc.system": "mcp",
28
+ "rpc.method": method,
29
+ # MCP semantic conventions
30
+ "mcp.method.name": method,
31
+ # FastMCP-specific attributes
32
+ "fastmcp.component.key": component_key,
33
+ }
34
+ if session_id:
35
+ attrs["mcp.session.id"] = session_id
36
+ if resource_uri:
37
+ attrs["mcp.resource.uri"] = resource_uri
38
+ span.set_attributes(attrs)
39
+ try:
40
+ yield span
41
+ except Exception as e:
42
+ span.record_exception(e)
43
+ span.set_status(Status(StatusCode.ERROR))
44
+ raise
45
+
46
+
47
+ __all__ = ["client_span"]
@@ -0,0 +1,38 @@
1
+ # Re-export all public APIs for backward compatibility
2
+ from mcp.server.fastmcp import FastMCP as FastMCP1Server
3
+
4
+ from fastmcp.client.transports.base import (
5
+ ClientTransport,
6
+ ClientTransportT,
7
+ SessionKwargs,
8
+ )
9
+ from fastmcp.client.transports.config import MCPConfigTransport
10
+ from fastmcp.client.transports.http import StreamableHttpTransport
11
+ from fastmcp.client.transports.inference import infer_transport
12
+ from fastmcp.client.transports.sse import SSETransport
13
+ from fastmcp.client.transports.memory import FastMCPTransport
14
+ from fastmcp.client.transports.stdio import (
15
+ FastMCPStdioTransport,
16
+ NodeStdioTransport,
17
+ NpxStdioTransport,
18
+ PythonStdioTransport,
19
+ StdioTransport,
20
+ UvStdioTransport,
21
+ UvxStdioTransport,
22
+ )
23
+ from fastmcp.server.server import FastMCP
24
+
25
+ __all__ = [
26
+ "ClientTransport",
27
+ "FastMCPStdioTransport",
28
+ "FastMCPTransport",
29
+ "NodeStdioTransport",
30
+ "NpxStdioTransport",
31
+ "PythonStdioTransport",
32
+ "SSETransport",
33
+ "StdioTransport",
34
+ "StreamableHttpTransport",
35
+ "UvStdioTransport",
36
+ "UvxStdioTransport",
37
+ "infer_transport",
38
+ ]