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,175 @@
1
+ """Transform that exposes prompts as tools.
2
+
3
+ This transform generates tools for listing and getting prompts, enabling
4
+ clients that only support tools to access prompt functionality.
5
+
6
+ Example:
7
+ ```python
8
+ from fastmcp import FastMCP
9
+ from fastmcp.server.transforms import PromptsAsTools
10
+
11
+ mcp = FastMCP("Server")
12
+ mcp.add_transform(PromptsAsTools(mcp))
13
+ # Now has list_prompts and get_prompt tools
14
+ ```
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ from collections.abc import Sequence
21
+ from typing import TYPE_CHECKING, Annotated, Any
22
+
23
+ from mcp.types import TextContent
24
+
25
+ from fastmcp.server.transforms import GetToolNext, Transform
26
+ from fastmcp.tools.tool import Tool
27
+ from fastmcp.utilities.versions import VersionSpec
28
+
29
+ if TYPE_CHECKING:
30
+ from fastmcp.server.providers.base import Provider
31
+
32
+ # Note: FastMCP imported inside tools to avoid circular import
33
+
34
+
35
+ class PromptsAsTools(Transform):
36
+ """Transform that adds tools for listing and getting prompts.
37
+
38
+ Generates two tools:
39
+ - `list_prompts`: Lists all prompts from the provider
40
+ - `get_prompt`: Gets a specific prompt with optional arguments
41
+
42
+ The transform captures a provider reference at construction and queries it
43
+ for prompts when the generated tools are called. When used with FastMCP,
44
+ the provider's auth and visibility filtering is automatically applied.
45
+
46
+ Example:
47
+ ```python
48
+ mcp = FastMCP("Server")
49
+ mcp.add_transform(PromptsAsTools(mcp))
50
+ # Now has list_prompts and get_prompt tools
51
+ ```
52
+ """
53
+
54
+ def __init__(self, provider: Provider) -> None:
55
+ """Initialize the transform with a provider reference.
56
+
57
+ Args:
58
+ provider: The provider to query for prompts. Typically this is
59
+ the same FastMCP server the transform is added to.
60
+ """
61
+ self._provider = provider
62
+
63
+ def __repr__(self) -> str:
64
+ return f"PromptsAsTools({self._provider!r})"
65
+
66
+ async def list_tools(self, tools: Sequence[Tool]) -> Sequence[Tool]:
67
+ """Add prompt tools to the tool list."""
68
+ return [
69
+ *tools,
70
+ self._make_list_prompts_tool(),
71
+ self._make_get_prompt_tool(),
72
+ ]
73
+
74
+ async def get_tool(
75
+ self, name: str, call_next: GetToolNext, *, version: VersionSpec | None = None
76
+ ) -> Tool | None:
77
+ """Get a tool by name, including generated prompt tools."""
78
+ # Check if it's one of our generated tools
79
+ if name == "list_prompts":
80
+ return self._make_list_prompts_tool()
81
+ if name == "get_prompt":
82
+ return self._make_get_prompt_tool()
83
+
84
+ # Otherwise delegate to downstream
85
+ return await call_next(name, version=version)
86
+
87
+ def _make_list_prompts_tool(self) -> Tool:
88
+ """Create the list_prompts tool."""
89
+ provider = self._provider
90
+
91
+ async def list_prompts() -> str:
92
+ """List all available prompts.
93
+
94
+ Returns JSON with prompt metadata including name, description,
95
+ and optional arguments.
96
+ """
97
+ prompts = await provider.list_prompts()
98
+
99
+ result: list[dict[str, Any]] = []
100
+ for p in prompts:
101
+ result.append(
102
+ {
103
+ "name": p.name,
104
+ "description": p.description,
105
+ "arguments": [
106
+ {
107
+ "name": arg.name,
108
+ "description": arg.description,
109
+ "required": arg.required,
110
+ }
111
+ for arg in (p.arguments or [])
112
+ ],
113
+ }
114
+ )
115
+
116
+ return json.dumps(result, indent=2)
117
+
118
+ return Tool.from_function(fn=list_prompts)
119
+
120
+ def _make_get_prompt_tool(self) -> Tool:
121
+ """Create the get_prompt tool."""
122
+ provider = self._provider
123
+
124
+ async def get_prompt(
125
+ name: Annotated[str, "The name of the prompt to get"],
126
+ arguments: Annotated[
127
+ dict[str, Any] | None,
128
+ "Optional arguments for the prompt",
129
+ ] = None,
130
+ ) -> str:
131
+ """Get a prompt by name with optional arguments.
132
+
133
+ Returns the rendered prompt as JSON with a messages array.
134
+ Arguments should be provided as a dict mapping argument names to values.
135
+ """
136
+ from fastmcp.server.server import FastMCP
137
+
138
+ # Use FastMCP.render_prompt() if available - runs middleware chain
139
+ if isinstance(provider, FastMCP):
140
+ result = await provider.render_prompt(name, arguments=arguments or {})
141
+ return _format_prompt_result(result)
142
+
143
+ # Fallback for plain providers - no middleware
144
+ prompt = await provider.get_prompt(name)
145
+ if prompt is None:
146
+ raise ValueError(f"Prompt not found: {name}")
147
+
148
+ result = await prompt._render(arguments or {})
149
+ return _format_prompt_result(result)
150
+
151
+ return Tool.from_function(fn=get_prompt)
152
+
153
+
154
+ def _format_prompt_result(result: Any) -> str:
155
+ """Format PromptResult for tool output.
156
+
157
+ Returns JSON with the messages array. Preserves embedded resources
158
+ as structured JSON objects.
159
+ """
160
+ messages = []
161
+ for msg in result.messages:
162
+ if isinstance(msg.content, TextContent):
163
+ content = msg.content.text
164
+ else:
165
+ # Preserve structured content (e.g., EmbeddedResource) as dict
166
+ content = msg.content.model_dump(mode="json", exclude_none=True)
167
+
168
+ messages.append(
169
+ {
170
+ "role": msg.role,
171
+ "content": content,
172
+ }
173
+ )
174
+
175
+ return json.dumps({"messages": messages}, indent=2)
@@ -0,0 +1,190 @@
1
+ """Transform that exposes resources as tools.
2
+
3
+ This transform generates tools for listing and reading resources, enabling
4
+ clients that only support tools to access resource functionality.
5
+
6
+ Example:
7
+ ```python
8
+ from fastmcp import FastMCP
9
+ from fastmcp.server.transforms import ResourcesAsTools
10
+
11
+ mcp = FastMCP("Server")
12
+ mcp.add_transform(ResourcesAsTools(mcp))
13
+ # Now has list_resources and read_resource tools
14
+ ```
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import base64
20
+ import json
21
+ from collections.abc import Sequence
22
+ from typing import TYPE_CHECKING, Annotated, Any
23
+
24
+ from fastmcp.server.transforms import GetToolNext, Transform
25
+ from fastmcp.tools.tool import Tool
26
+ from fastmcp.utilities.versions import VersionSpec
27
+
28
+ if TYPE_CHECKING:
29
+ from fastmcp.server.providers.base import Provider
30
+
31
+
32
+ class ResourcesAsTools(Transform):
33
+ """Transform that adds tools for listing and reading resources.
34
+
35
+ Generates two tools:
36
+ - `list_resources`: Lists all resources and templates from the provider
37
+ - `read_resource`: Reads a resource by URI
38
+
39
+ The transform captures a provider reference at construction and queries it
40
+ for resources when the generated tools are called. When used with FastMCP,
41
+ the provider's auth and visibility filtering is automatically applied.
42
+
43
+ Example:
44
+ ```python
45
+ mcp = FastMCP("Server")
46
+ mcp.add_transform(ResourcesAsTools(mcp))
47
+ # Now has list_resources and read_resource tools
48
+ ```
49
+ """
50
+
51
+ def __init__(self, provider: Provider) -> None:
52
+ """Initialize the transform with a provider reference.
53
+
54
+ Args:
55
+ provider: The provider to query for resources. Typically this is
56
+ the same FastMCP server the transform is added to.
57
+ """
58
+ self._provider = provider
59
+
60
+ def __repr__(self) -> str:
61
+ return f"ResourcesAsTools({self._provider!r})"
62
+
63
+ async def list_tools(self, tools: Sequence[Tool]) -> Sequence[Tool]:
64
+ """Add resource tools to the tool list."""
65
+ return [
66
+ *tools,
67
+ self._make_list_resources_tool(),
68
+ self._make_read_resource_tool(),
69
+ ]
70
+
71
+ async def get_tool(
72
+ self, name: str, call_next: GetToolNext, *, version: VersionSpec | None = None
73
+ ) -> Tool | None:
74
+ """Get a tool by name, including generated resource tools."""
75
+ # Check if it's one of our generated tools
76
+ if name == "list_resources":
77
+ return self._make_list_resources_tool()
78
+ if name == "read_resource":
79
+ return self._make_read_resource_tool()
80
+
81
+ # Otherwise delegate to downstream
82
+ return await call_next(name, version=version)
83
+
84
+ def _make_list_resources_tool(self) -> Tool:
85
+ """Create the list_resources tool."""
86
+ provider = self._provider
87
+
88
+ async def list_resources() -> str:
89
+ """List all available resources and resource templates.
90
+
91
+ Returns JSON with resource metadata. Static resources have a 'uri' field,
92
+ while templates have a 'uri_template' field with placeholders like {name}.
93
+ """
94
+ resources = await provider.list_resources()
95
+ templates = await provider.list_resource_templates()
96
+
97
+ result: list[dict[str, Any]] = []
98
+
99
+ # Static resources
100
+ for r in resources:
101
+ result.append(
102
+ {
103
+ "uri": str(r.uri),
104
+ "name": r.name,
105
+ "description": r.description,
106
+ "mime_type": r.mime_type,
107
+ }
108
+ )
109
+
110
+ # Resource templates (URI contains placeholders like {name})
111
+ for t in templates:
112
+ result.append(
113
+ {
114
+ "uri_template": t.uri_template,
115
+ "name": t.name,
116
+ "description": t.description,
117
+ }
118
+ )
119
+
120
+ return json.dumps(result, indent=2)
121
+
122
+ return Tool.from_function(fn=list_resources)
123
+
124
+ def _make_read_resource_tool(self) -> Tool:
125
+ """Create the read_resource tool."""
126
+ provider = self._provider
127
+
128
+ async def read_resource(
129
+ uri: Annotated[str, "The URI of the resource to read"],
130
+ ) -> str:
131
+ """Read a resource by its URI.
132
+
133
+ For static resources, provide the exact URI. For templated resources,
134
+ provide the URI with template parameters filled in.
135
+
136
+ Returns the resource content as a string. Binary content is
137
+ base64-encoded.
138
+ """
139
+ from fastmcp import FastMCP
140
+
141
+ # Use FastMCP.read_resource() if available - runs middleware chain
142
+ if isinstance(provider, FastMCP):
143
+ result = await provider.read_resource(uri)
144
+ return _format_result(result)
145
+
146
+ # Fallback for plain providers - no middleware
147
+ resource = await provider.get_resource(uri)
148
+ if resource is not None:
149
+ result = await resource._read()
150
+ return _format_result(result)
151
+
152
+ template = await provider.get_resource_template(uri)
153
+ if template is not None:
154
+ params = template.matches(uri)
155
+ if params is not None:
156
+ result = await template._read(uri, params)
157
+ return _format_result(result)
158
+
159
+ raise ValueError(f"Resource not found: {uri}")
160
+
161
+ return Tool.from_function(fn=read_resource)
162
+
163
+
164
+ def _format_result(result: Any) -> str:
165
+ """Format ResourceResult for tool output.
166
+
167
+ Single text content is returned as-is. Single binary content is base64-encoded.
168
+ Multiple contents are JSON-encoded with each item containing content and mime_type.
169
+ """
170
+ # result is a ResourceResult with .contents list
171
+ if len(result.contents) == 1:
172
+ content = result.contents[0].content
173
+ if isinstance(content, bytes):
174
+ return base64.b64encode(content).decode()
175
+ return content
176
+
177
+ # Multiple contents - JSON encode
178
+ return json.dumps(
179
+ [
180
+ {
181
+ "content": (
182
+ c.content
183
+ if isinstance(c.content, str)
184
+ else base64.b64encode(c.content).decode()
185
+ ),
186
+ "mime_type": c.mime_type,
187
+ }
188
+ for c in result.contents
189
+ ]
190
+ )
@@ -0,0 +1,96 @@
1
+ """Transform for applying tool transformations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from typing import TYPE_CHECKING
7
+
8
+ from fastmcp.server.transforms import GetToolNext, Transform
9
+ from fastmcp.tools.tool_transform import ToolTransformConfig
10
+ from fastmcp.utilities.versions import VersionSpec
11
+
12
+ if TYPE_CHECKING:
13
+ from fastmcp.tools.tool import Tool
14
+
15
+
16
+ class ToolTransform(Transform):
17
+ """Applies tool transformations to modify tool schemas.
18
+
19
+ Wraps ToolTransformConfig to apply argument renames, schema changes,
20
+ hidden arguments, and other transformations at the transform level.
21
+
22
+ Example:
23
+ ```python
24
+ transform = ToolTransform({
25
+ "my_tool": ToolTransformConfig(
26
+ name="renamed_tool",
27
+ arguments={"old_arg": ArgTransformConfig(name="new_arg")}
28
+ )
29
+ })
30
+ ```
31
+ """
32
+
33
+ def __init__(self, transforms: dict[str, ToolTransformConfig]) -> None:
34
+ """Initialize ToolTransform.
35
+
36
+ Args:
37
+ transforms: Map of original tool name → transform config.
38
+ """
39
+ self._transforms = transforms
40
+
41
+ # Build reverse mapping: final_name → original_name
42
+ self._name_reverse: dict[str, str] = {}
43
+ for original_name, config in transforms.items():
44
+ final_name = config.name if config.name else original_name
45
+ self._name_reverse[final_name] = original_name
46
+
47
+ # Validate no duplicate target names
48
+ seen_targets: dict[str, str] = {}
49
+ for original_name, config in transforms.items():
50
+ target = config.name if config.name else original_name
51
+ if target in seen_targets:
52
+ raise ValueError(
53
+ f"ToolTransform has duplicate target name {target!r}: "
54
+ f"both {seen_targets[target]!r} and {original_name!r} map to it"
55
+ )
56
+ seen_targets[target] = original_name
57
+
58
+ def __repr__(self) -> str:
59
+ names = list(self._transforms.keys())
60
+ if len(names) <= 3:
61
+ return f"ToolTransform({names!r})"
62
+ return f"ToolTransform({names[:3]!r}... +{len(names) - 3} more)"
63
+
64
+ async def list_tools(self, tools: Sequence[Tool]) -> Sequence[Tool]:
65
+ """Apply transforms to matching tools."""
66
+ result: list[Tool] = []
67
+ for tool in tools:
68
+ if tool.name in self._transforms:
69
+ transformed = self._transforms[tool.name].apply(tool)
70
+ result.append(transformed)
71
+ else:
72
+ result.append(tool)
73
+ return result
74
+
75
+ async def get_tool(
76
+ self, name: str, call_next: GetToolNext, *, version: VersionSpec | None = None
77
+ ) -> Tool | None:
78
+ """Get tool by transformed name."""
79
+ # Check if this name is a transformed name
80
+ original_name = self._name_reverse.get(name, name)
81
+
82
+ # Get the original tool
83
+ tool = await call_next(original_name, version=version)
84
+ if tool is None:
85
+ return None
86
+
87
+ # Apply transform if applicable
88
+ if original_name in self._transforms:
89
+ transformed = self._transforms[original_name].apply(tool)
90
+ # Only return if requested name matches transformed name
91
+ if transformed.name == name:
92
+ return transformed
93
+ return None
94
+
95
+ # No transform, return as-is only if name matches
96
+ return tool if tool.name == name else None
@@ -0,0 +1,124 @@
1
+ """Version filter transform for filtering components by version range."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from typing import TYPE_CHECKING
7
+
8
+ from fastmcp.server.transforms import (
9
+ GetPromptNext,
10
+ GetResourceNext,
11
+ GetResourceTemplateNext,
12
+ GetToolNext,
13
+ Transform,
14
+ )
15
+ from fastmcp.utilities.versions import VersionSpec
16
+
17
+ if TYPE_CHECKING:
18
+ from fastmcp.prompts.prompt import Prompt
19
+ from fastmcp.resources.resource import Resource
20
+ from fastmcp.resources.template import ResourceTemplate
21
+ from fastmcp.tools.tool import Tool
22
+
23
+
24
+ class VersionFilter(Transform):
25
+ """Filters components by version range.
26
+
27
+ When applied to a provider or server, only components within the version
28
+ range are visible. Within that filtered set, the highest version of each
29
+ component is exposed to clients (standard deduplication behavior).
30
+
31
+ Parameters mirror comparison operators for clarity:
32
+
33
+ # Versions < 3.0 (v1 and v2)
34
+ server.add_transform(VersionFilter(version_lt="3.0"))
35
+
36
+ # Versions >= 2.0 and < 3.0 (only v2.x)
37
+ server.add_transform(VersionFilter(version_gte="2.0", version_lt="3.0"))
38
+
39
+ Works with any version string - PEP 440 (1.0, 2.0) or dates (2025-01-01).
40
+
41
+ Args:
42
+ version_gte: Versions >= this value pass through.
43
+ version_lt: Versions < this value pass through.
44
+ """
45
+
46
+ def __init__(
47
+ self,
48
+ *,
49
+ version_gte: str | None = None,
50
+ version_lt: str | None = None,
51
+ ) -> None:
52
+ if version_gte is None and version_lt is None:
53
+ raise ValueError(
54
+ "At least one of version_gte or version_lt must be specified"
55
+ )
56
+ self.version_gte = version_gte
57
+ self.version_lt = version_lt
58
+ self._spec = VersionSpec(gte=version_gte, lt=version_lt)
59
+
60
+ def __repr__(self) -> str:
61
+ parts = []
62
+ if self.version_gte:
63
+ parts.append(f"version_gte={self.version_gte!r}")
64
+ if self.version_lt:
65
+ parts.append(f"version_lt={self.version_lt!r}")
66
+ return f"VersionFilter({', '.join(parts)})"
67
+
68
+ # -------------------------------------------------------------------------
69
+ # Tools
70
+ # -------------------------------------------------------------------------
71
+
72
+ async def list_tools(self, tools: Sequence[Tool]) -> Sequence[Tool]:
73
+ return [t for t in tools if self._spec.matches(t.version)]
74
+
75
+ async def get_tool(
76
+ self, name: str, call_next: GetToolNext, *, version: VersionSpec | None = None
77
+ ) -> Tool | None:
78
+ return await call_next(name, version=self._spec.intersect(version))
79
+
80
+ # -------------------------------------------------------------------------
81
+ # Resources
82
+ # -------------------------------------------------------------------------
83
+
84
+ async def list_resources(self, resources: Sequence[Resource]) -> Sequence[Resource]:
85
+ return [r for r in resources if self._spec.matches(r.version)]
86
+
87
+ async def get_resource(
88
+ self,
89
+ uri: str,
90
+ call_next: GetResourceNext,
91
+ *,
92
+ version: VersionSpec | None = None,
93
+ ) -> Resource | None:
94
+ return await call_next(uri, version=self._spec.intersect(version))
95
+
96
+ # -------------------------------------------------------------------------
97
+ # Resource Templates
98
+ # -------------------------------------------------------------------------
99
+
100
+ async def list_resource_templates(
101
+ self, templates: Sequence[ResourceTemplate]
102
+ ) -> Sequence[ResourceTemplate]:
103
+ return [t for t in templates if self._spec.matches(t.version)]
104
+
105
+ async def get_resource_template(
106
+ self,
107
+ uri: str,
108
+ call_next: GetResourceTemplateNext,
109
+ *,
110
+ version: VersionSpec | None = None,
111
+ ) -> ResourceTemplate | None:
112
+ return await call_next(uri, version=self._spec.intersect(version))
113
+
114
+ # -------------------------------------------------------------------------
115
+ # Prompts
116
+ # -------------------------------------------------------------------------
117
+
118
+ async def list_prompts(self, prompts: Sequence[Prompt]) -> Sequence[Prompt]:
119
+ return [p for p in prompts if self._spec.matches(p.version)]
120
+
121
+ async def get_prompt(
122
+ self, name: str, call_next: GetPromptNext, *, version: VersionSpec | None = None
123
+ ) -> Prompt | None:
124
+ return await call_next(name, version=self._spec.intersect(version))