fastmcp 2.14.3__py3-none-any.whl → 2.14.5__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.
@@ -36,7 +36,6 @@ from fastmcp.client.auth.oauth import OAuth
36
36
  from fastmcp.mcp_config import MCPConfig, infer_transport_type_from_url
37
37
  from fastmcp.server.dependencies import get_http_headers
38
38
  from fastmcp.server.server import FastMCP
39
- from fastmcp.server.tasks.capabilities import get_task_capabilities
40
39
  from fastmcp.utilities.logging import get_logger
41
40
  from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment
42
41
 
@@ -900,16 +899,11 @@ class FastMCPTransport(ClientTransport):
900
899
  anyio.create_task_group() as tg,
901
900
  _enter_server_lifespan(server=self.server),
902
901
  ):
903
- # Build experimental capabilities
904
- experimental_capabilities = get_task_capabilities()
905
-
906
902
  tg.start_soon(
907
903
  lambda: self.server._mcp_server.run(
908
904
  server_read,
909
905
  server_write,
910
- self.server._mcp_server.create_initialization_options(
911
- experimental_capabilities=experimental_capabilities
912
- ),
906
+ self.server._mcp_server.create_initialization_options(),
913
907
  raise_exceptions=self.raise_exceptions,
914
908
  )
915
909
  )
fastmcp/server/http.py CHANGED
@@ -21,7 +21,6 @@ from starlette.types import Lifespan, Receive, Scope, Send
21
21
 
22
22
  from fastmcp.server.auth import AuthProvider
23
23
  from fastmcp.server.auth.middleware import RequireAuthMiddleware
24
- from fastmcp.server.tasks.capabilities import get_task_capabilities
25
24
  from fastmcp.utilities.logging import get_logger
26
25
 
27
26
  if TYPE_CHECKING:
@@ -160,15 +159,10 @@ def create_sse_app(
160
159
  # Create handler for SSE connections
161
160
  async def handle_sse(scope: Scope, receive: Receive, send: Send) -> Response:
162
161
  async with sse.connect_sse(scope, receive, send) as streams:
163
- # Build experimental capabilities
164
- experimental_capabilities = get_task_capabilities()
165
-
166
162
  await server._mcp_server.run(
167
163
  streams[0],
168
164
  streams[1],
169
- server._mcp_server.create_initialization_options(
170
- experimental_capabilities=experimental_capabilities
171
- ),
165
+ server._mcp_server.create_initialization_options(),
172
166
  )
173
167
  return Response()
174
168
 
@@ -163,6 +163,31 @@ class LowLevelServer(_Server[LifespanResultT, RequestT]):
163
163
  **kwargs,
164
164
  )
165
165
 
166
+ def get_capabilities(
167
+ self,
168
+ notification_options: NotificationOptions,
169
+ experimental_capabilities: dict[str, dict[str, Any]],
170
+ ) -> mcp.types.ServerCapabilities:
171
+ """Override to set capabilities.tasks as a first-class field per SEP-1686.
172
+
173
+ This ensures task capabilities appear in capabilities.tasks instead of
174
+ capabilities.experimental.tasks, which is required by the MCP spec and
175
+ enables proper task detection by clients like VS Code Copilot 1.107+.
176
+ """
177
+ from fastmcp.server.tasks.capabilities import get_task_capabilities
178
+
179
+ # Get base capabilities from SDK (pass empty dict for experimental)
180
+ # since we'll set tasks as a first-class field instead
181
+ capabilities = super().get_capabilities(
182
+ notification_options,
183
+ experimental_capabilities or {},
184
+ )
185
+
186
+ # Set tasks as a first-class field (not experimental) per SEP-1686
187
+ capabilities.tasks = get_task_capabilities()
188
+
189
+ return capabilities
190
+
166
191
  async def run(
167
192
  self,
168
193
  read_stream: MemoryObjectReceiveStream[SessionMessage | Exception],
fastmcp/server/server.py CHANGED
@@ -75,7 +75,6 @@ from fastmcp.server.http import (
75
75
  )
76
76
  from fastmcp.server.low_level import LowLevelServer
77
77
  from fastmcp.server.middleware import Middleware, MiddlewareContext
78
- from fastmcp.server.tasks.capabilities import get_task_capabilities
79
78
  from fastmcp.server.tasks.config import TaskConfig
80
79
  from fastmcp.server.tasks.handlers import (
81
80
  handle_prompt_as_task,
@@ -2508,9 +2507,6 @@ class FastMCP(Generic[LifespanResultT]):
2508
2507
  f"Starting MCP server {self.name!r} with transport 'stdio'"
2509
2508
  )
2510
2509
 
2511
- # Build experimental capabilities
2512
- experimental_capabilities = get_task_capabilities()
2513
-
2514
2510
  await self._mcp_server.run(
2515
2511
  read_stream,
2516
2512
  write_stream,
@@ -2518,7 +2514,6 @@ class FastMCP(Generic[LifespanResultT]):
2518
2514
  notification_options=NotificationOptions(
2519
2515
  tools_changed=True
2520
2516
  ),
2521
- experimental_capabilities=experimental_capabilities,
2522
2517
  ),
2523
2518
  )
2524
2519
 
@@ -1,22 +1,30 @@
1
1
  """SEP-1686 task capabilities declaration."""
2
2
 
3
- from typing import Any
3
+ from mcp.types import (
4
+ ServerTasksCapability,
5
+ ServerTasksRequestsCapability,
6
+ TasksCallCapability,
7
+ TasksCancelCapability,
8
+ TasksListCapability,
9
+ TasksToolsCapability,
10
+ )
4
11
 
5
12
 
6
- def get_task_capabilities() -> dict[str, Any]:
7
- """Return the SEP-1686 task capabilities structure.
13
+ def get_task_capabilities() -> ServerTasksCapability:
14
+ """Return the SEP-1686 task capabilities.
8
15
 
9
- This is the standard capabilities map advertised to clients,
10
- declaring support for list, cancel, and request operations.
16
+ Returns task capabilities as a first-class ServerCapabilities field,
17
+ declaring support for list, cancel, and request operations per SEP-1686.
18
+
19
+ Note: prompts/resources are passed via extra_data since the SDK types
20
+ don't include them yet (FastMCP supports them ahead of the spec).
11
21
  """
12
- return {
13
- "tasks": {
14
- "list": {},
15
- "cancel": {},
16
- "requests": {
17
- "tools": {"call": {}},
18
- "prompts": {"get": {}},
19
- "resources": {"read": {}},
20
- },
21
- }
22
- }
22
+ return ServerTasksCapability(
23
+ list=TasksListCapability(),
24
+ cancel=TasksCancelCapability(),
25
+ requests=ServerTasksRequestsCapability(
26
+ tools=TasksToolsCapability(call=TasksCallCapability()),
27
+ prompts={"get": {}}, # type: ignore[call-arg] # extra_data for forward compat
28
+ resources={"read": {}}, # type: ignore[call-arg] # extra_data for forward compat
29
+ ),
30
+ )
@@ -3,6 +3,130 @@ from __future__ import annotations
3
3
  from collections import defaultdict
4
4
  from typing import Any
5
5
 
6
+ from jsonref import JsonRefError, replace_refs
7
+
8
+
9
+ def dereference_refs(schema: dict[str, Any]) -> dict[str, Any]:
10
+ """Resolve all $ref references in a JSON schema by inlining definitions.
11
+
12
+ This function resolves $ref references that point to $defs, replacing them
13
+ with the actual definition content while preserving sibling keywords (like
14
+ description, default, examples) that Pydantic places alongside $ref.
15
+
16
+ This is necessary because some MCP clients (e.g., VS Code Copilot) don't
17
+ properly handle $ref in tool input schemas.
18
+
19
+ For self-referencing/circular schemas where full dereferencing is not possible,
20
+ this function falls back to resolving only the root-level $ref while preserving
21
+ $defs for nested references.
22
+
23
+ Args:
24
+ schema: JSON schema dict that may contain $ref references
25
+
26
+ Returns:
27
+ A new schema dict with $ref resolved where possible and $defs removed
28
+ when no longer needed
29
+
30
+ Example:
31
+ >>> schema = {
32
+ ... "$defs": {"Category": {"enum": ["a", "b"], "type": "string"}},
33
+ ... "properties": {"cat": {"$ref": "#/$defs/Category", "default": "a"}}
34
+ ... }
35
+ >>> resolved = dereference_refs(schema)
36
+ >>> # Result: {"properties": {"cat": {"enum": ["a", "b"], "type": "string", "default": "a"}}}
37
+ """
38
+ try:
39
+ # Use jsonref to resolve all $ref references
40
+ # proxies=False returns plain dicts (not proxy objects)
41
+ # lazy_load=False resolves immediately
42
+ dereferenced = replace_refs(schema, proxies=False, lazy_load=False)
43
+
44
+ # Merge sibling keywords that were lost during dereferencing
45
+ # Pydantic puts description, default, examples as siblings to $ref
46
+ defs = schema.get("$defs", {})
47
+ merged = _merge_ref_siblings(schema, dereferenced, defs)
48
+ # Type assertion: top-level schema is always a dict
49
+ assert isinstance(merged, dict)
50
+ dereferenced = merged
51
+
52
+ # Remove $defs since all references have been resolved
53
+ if "$defs" in dereferenced:
54
+ dereferenced = {k: v for k, v in dereferenced.items() if k != "$defs"}
55
+
56
+ return dereferenced
57
+
58
+ except JsonRefError:
59
+ # Self-referencing/circular schemas can't be fully dereferenced
60
+ # Fall back to resolving only root-level $ref (for MCP spec compliance)
61
+ return resolve_root_ref(schema)
62
+
63
+
64
+ def _merge_ref_siblings(
65
+ original: Any,
66
+ dereferenced: Any,
67
+ defs: dict[str, Any],
68
+ visited: set[str] | None = None,
69
+ ) -> Any:
70
+ """Merge sibling keywords from original $ref nodes into dereferenced schema.
71
+
72
+ When jsonref resolves $ref, it replaces the entire node with the referenced
73
+ definition, losing any sibling keywords like description, default, or examples.
74
+ This function walks both trees in parallel and merges those siblings back.
75
+
76
+ Args:
77
+ original: The original schema with $ref and potential siblings
78
+ dereferenced: The schema after jsonref processing
79
+ defs: The $defs from the original schema, for looking up referenced definitions
80
+ visited: Set of definition names already being processed (prevents cycles)
81
+
82
+ Returns:
83
+ The dereferenced schema with sibling keywords restored
84
+ """
85
+ if visited is None:
86
+ visited = set()
87
+
88
+ if isinstance(original, dict) and isinstance(dereferenced, dict):
89
+ # Check if original had a $ref
90
+ if "$ref" in original:
91
+ ref = original["$ref"]
92
+ siblings = {k: v for k, v in original.items() if k not in ("$ref", "$defs")}
93
+
94
+ # Look up the referenced definition to process its nested siblings
95
+ if isinstance(ref, str) and ref.startswith("#/$defs/"):
96
+ def_name = ref.split("/")[-1]
97
+ # Prevent infinite recursion on circular references
98
+ if def_name in defs and def_name not in visited:
99
+ # Recursively process the definition's content for nested siblings
100
+ dereferenced = _merge_ref_siblings(
101
+ defs[def_name], dereferenced, defs, visited | {def_name}
102
+ )
103
+
104
+ if siblings:
105
+ # Merge local siblings, which take precedence
106
+ merged = dict(dereferenced)
107
+ merged.update(siblings)
108
+ return merged
109
+ return dereferenced
110
+
111
+ # Recurse into nested structures
112
+ result = {}
113
+ for key, value in dereferenced.items():
114
+ if key in original:
115
+ result[key] = _merge_ref_siblings(original[key], value, defs, visited)
116
+ else:
117
+ result[key] = value
118
+ return result
119
+
120
+ elif isinstance(original, list) and isinstance(dereferenced, list):
121
+ # Process list items in parallel
122
+ min_len = min(len(original), len(dereferenced))
123
+ return [
124
+ _merge_ref_siblings(o, d, defs, visited)
125
+ for o, d in zip(original[:min_len], dereferenced[:min_len], strict=False)
126
+ ] + dereferenced[min_len:]
127
+
128
+ return dereferenced
129
+
6
130
 
7
131
  def resolve_root_ref(schema: dict[str, Any]) -> dict[str, Any]:
8
132
  """Resolve $ref at root level to meet MCP spec requirements.
@@ -43,7 +167,7 @@ def resolve_root_ref(schema: dict[str, Any]) -> dict[str, Any]:
43
167
  return schema
44
168
 
45
169
 
46
- def _prune_param(schema: dict, param: str) -> dict:
170
+ def _prune_param(schema: dict[str, Any], param: str) -> dict[str, Any]:
47
171
  """Return a new schema with *param* removed from `properties`, `required`,
48
172
  and (if no longer referenced) `$defs`.
49
173
  """
@@ -65,11 +189,11 @@ def _prune_param(schema: dict, param: str) -> dict:
65
189
 
66
190
 
67
191
  def _single_pass_optimize(
68
- schema: dict,
192
+ schema: dict[str, Any],
69
193
  prune_titles: bool = False,
70
194
  prune_additional_properties: bool = False,
71
195
  prune_defs: bool = True,
72
- ) -> dict:
196
+ ) -> dict[str, Any]:
73
197
  """
74
198
  Optimize JSON schemas in a single traversal for better performance.
75
199
 
@@ -238,12 +362,12 @@ def _single_pass_optimize(
238
362
 
239
363
 
240
364
  def compress_schema(
241
- schema: dict,
365
+ schema: dict[str, Any],
242
366
  prune_params: list[str] | None = None,
243
367
  prune_defs: bool = True,
244
368
  prune_additional_properties: bool = True,
245
369
  prune_titles: bool = False,
246
- ) -> dict:
370
+ ) -> dict[str, Any]:
247
371
  """
248
372
  Remove the given parameters from the schema.
249
373
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastmcp
3
- Version: 2.14.3
3
+ Version: 2.14.5
4
4
  Summary: The fast, Pythonic way to build MCP servers and clients.
5
5
  Project-URL: Homepage, https://gofastmcp.com
6
6
  Project-URL: Repository, https://github.com/jlowin/fastmcp
@@ -22,13 +22,15 @@ Requires-Dist: authlib>=1.6.5
22
22
  Requires-Dist: cyclopts>=4.0.0
23
23
  Requires-Dist: exceptiongroup>=1.2.2
24
24
  Requires-Dist: httpx>=0.28.1
25
+ Requires-Dist: jsonref>=1.1.0
25
26
  Requires-Dist: jsonschema-path>=0.3.4
26
27
  Requires-Dist: mcp<2.0,>=1.24.0
27
28
  Requires-Dist: openapi-pydantic>=0.5.1
29
+ Requires-Dist: packaging>=20.0
28
30
  Requires-Dist: platformdirs>=4.0.0
29
31
  Requires-Dist: py-key-value-aio[disk,keyring,memory]<0.4.0,>=0.3.0
30
32
  Requires-Dist: pydantic[email]>=2.11.7
31
- Requires-Dist: pydocket>=0.16.6
33
+ Requires-Dist: pydocket>=0.17.2
32
34
  Requires-Dist: pyperclip>=1.9.0
33
35
  Requires-Dist: python-dotenv>=1.1.0
34
36
  Requires-Dist: rich>=13.9.4
@@ -25,7 +25,7 @@ fastmcp/client/oauth_callback.py,sha256=3xqL5_HD1QS9eGfw31HzoVF94QQelq_0TTqS7qWD
25
25
  fastmcp/client/progress.py,sha256=WjLLDbUKMsx8DK-fqO7AGsXb83ak-6BMrLvzzznGmcI,1043
26
26
  fastmcp/client/roots.py,sha256=Uap1RSr3uEeQRZTHkEttkhTI2fOA8IeDcRSggtZp9aY,2568
27
27
  fastmcp/client/tasks.py,sha256=zjiTfvjU9NaA4e3XTBGHsqvSfBRR19UqZMIUhJ_nQTo,19480
28
- fastmcp/client/transports.py,sha256=g-LDLwTw-t8lkV0u_nFL3XgC0L_sccD98R7OnU5d9F0,43583
28
+ fastmcp/client/transports.py,sha256=iFoivucnJb0LoDVu9HbEgWta3KcxRjM3arR4ebZOZco,43291
29
29
  fastmcp/client/auth/__init__.py,sha256=4DNsfp4iaQeBcpds0JDdMn6Mmfud44stWLsret0sVKY,91
30
30
  fastmcp/client/auth/bearer.py,sha256=MFEFqcH6u_V86msYiOsEFKN5ks1V9BnBNiPsPLHUTqo,399
31
31
  fastmcp/client/auth/oauth.py,sha256=PXtWFFSqR29QZ_ZYk74EIRHdj_qOGP2yerXb0HDw2ns,12745
@@ -65,10 +65,10 @@ fastmcp/server/context.py,sha256=vivwwI4u7RuaYMivQ0IlqqHDQxZo682ZMckt-5muC3A,431
65
65
  fastmcp/server/dependencies.py,sha256=gRc60PhEvna9rlqMW-ZlYNszPlUeEeOWT5winYGNH2A,20928
66
66
  fastmcp/server/elicitation.py,sha256=CmHi_SERmhEcNjwnM90_HGihUKlCM3RPGHI0uns2t7M,17912
67
67
  fastmcp/server/event_store.py,sha256=ZiBbrUQHw9--G8lzK1qLZmUAF2le2XchFen4pGbFKsE,6170
68
- fastmcp/server/http.py,sha256=_HjMSYWH8mfKugDODU4iV0AhKDU2VRc40tS56L6i-_s,12737
69
- fastmcp/server/low_level.py,sha256=o3jDf5SuZBQeurhLWRzaSVCnvrmaKMH_w-TbHk6BuZ4,7963
68
+ fastmcp/server/http.py,sha256=mQnb9moDqgzqgo-H30Qii6EcOSMhPjrh4rL3zr5ES4g,12469
69
+ fastmcp/server/low_level.py,sha256=afTlucPVSOaG9N68D8CGZh4oU1tc9g_IpLfX7crZB4o,9000
70
70
  fastmcp/server/proxy.py,sha256=bsgVkcdlRtVK3bB4EeVKrq4PLjIoUvWN_hgzr1hq8yE,26837
71
- fastmcp/server/server.py,sha256=MjNPByytoVMyNvxjn9K26vDB1k7hKeXvOa52K3uivT4,121608
71
+ fastmcp/server/server.py,sha256=HCUJBE0E47gOx_f1o6XJn5U-JChc_wlKtFK5EIDnNjc,121332
72
72
  fastmcp/server/auth/__init__.py,sha256=MTZvDKEUMqjs9-raRN0h8Zjx8pWFXs_iSRbB1UqBUqU,527
73
73
  fastmcp/server/auth/auth.py,sha256=Bvm98USOP0A0yTckKCN7yHJHS4JgCG804W5cQx6GgO4,20430
74
74
  fastmcp/server/auth/jwt_issuer.py,sha256=lJYvrpC1ygI4jkoJlL_nTH6m7FKdTw2lbEycKo4eHLY,7197
@@ -110,7 +110,7 @@ fastmcp/server/sampling/__init__.py,sha256=u9jDHSE_yz6kTzbFqIOXqnM0PfIAiP-peAjHJ
110
110
  fastmcp/server/sampling/run.py,sha256=1FIg9TMcvilQcgW0i00xlpgd7Yz6b814ntG3ihtFo6g,10469
111
111
  fastmcp/server/sampling/sampling_tool.py,sha256=YltN7-NcMXUk6cFbuOQmuJ980bmQyh83qgFo0TogP5Q,3311
112
112
  fastmcp/server/tasks/__init__.py,sha256=VizXvmXgA3SvrApQ6PSz4z1TPA9B6uROvmWeGSYOJ0I,530
113
- fastmcp/server/tasks/capabilities.py,sha256=-8QMBjs6HZuQdUNmOrNEBvJs-opGptIyxOODU0TGGFE,574
113
+ fastmcp/server/tasks/capabilities.py,sha256=ut7PrKFUPu9FAO9IzqmoS2iQ7ByQWZOAqsMXxyS34dI,1074
114
114
  fastmcp/server/tasks/config.py,sha256=msPkUuxnZKuqSj21Eh8m5Cwq0htwUzTCeoWsnbvKGkk,3006
115
115
  fastmcp/server/tasks/converters.py,sha256=ON7c8gOMjBYiQoyk_vkymI8J01ccoYzizDwtgIIqIZQ,6701
116
116
  fastmcp/server/tasks/handlers.py,sha256=1KTyfPgpQ-6YRfiK3s10sqa2pkRB0tR6ZZzb55KLZDk,12884
@@ -128,7 +128,7 @@ fastmcp/utilities/components.py,sha256=fF4M9cdqbZTlDAZ0hltcTTg_8IU2jNSzOyH4oqH49
128
128
  fastmcp/utilities/exceptions.py,sha256=7Z9j5IzM5rT27BC1Mcn8tkS-bjqCYqMKwb2MMTaxJYU,1350
129
129
  fastmcp/utilities/http.py,sha256=1ns1ymBS-WSxbZjGP6JYjSO52Wa_ls4j4WbnXiupoa4,245
130
130
  fastmcp/utilities/inspect.py,sha256=3wYUuQH1xCCCdzZwALHNqaRABH6iqpA43dIXEhqVb5Q,18030
131
- fastmcp/utilities/json_schema.py,sha256=H8RNucfulnXqYjCzRrlaWCBfToHmJGc7M32VJu5q7Eo,10587
131
+ fastmcp/utilities/json_schema.py,sha256=rhSub4bxP_ACW8VJu1SoQCbL32wQNV8PUJlzt1uUR5g,15701
132
132
  fastmcp/utilities/json_schema_type.py,sha256=5cf1ZeHzqirrGx62kznqmgAWk0uCc29REVKcDRBeJX0,22348
133
133
  fastmcp/utilities/logging.py,sha256=61wVk5yQ62km3K8kZtkKtT_3EN26VL85GYW0aMtnwKA,7175
134
134
  fastmcp/utilities/mcp_config.py,sha256=lVllZtAXZ3Zy78D40aXN-S5fs-ms0lgryL1tY2WzwCY,1783
@@ -154,8 +154,8 @@ fastmcp/utilities/openapi/json_schema_converter.py,sha256=PxaYpgHBsdDTT0XSP6s4RZ
154
154
  fastmcp/utilities/openapi/models.py,sha256=-kfndwZSe92tVtKAgOuFn5rk1tN7oydCZKtLOEMEalA,2805
155
155
  fastmcp/utilities/openapi/parser.py,sha256=qsa68Ro1c8ov77kdEP20IwZqD74E4IGKjtfeIkn3HdE,34338
156
156
  fastmcp/utilities/openapi/schemas.py,sha256=UXHHjkJyDp1WwJ8kowYt79wnwdbDwAbUFfqwcIY6mIM,23359
157
- fastmcp-2.14.3.dist-info/METADATA,sha256=Oj-OVe1cTUGmHVkFAlA6Zu_g4a92JnghvjJ-YbKwoAc,20771
158
- fastmcp-2.14.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
159
- fastmcp-2.14.3.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
160
- fastmcp-2.14.3.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
161
- fastmcp-2.14.3.dist-info/RECORD,,
157
+ fastmcp-2.14.5.dist-info/METADATA,sha256=CDVpbbk9nijkmYhDm1bNNl0MrbalpCq2Yb_RTcfLfns,20832
158
+ fastmcp-2.14.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
159
+ fastmcp-2.14.5.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
160
+ fastmcp-2.14.5.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
161
+ fastmcp-2.14.5.dist-info/RECORD,,