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,131 @@
1
+ """Server-side telemetry helpers."""
2
+
3
+ from collections.abc import Generator
4
+ from contextlib import contextmanager
5
+
6
+ from mcp.server.lowlevel.server import request_ctx
7
+ from opentelemetry.context import Context
8
+ from opentelemetry.trace import Span, SpanKind, Status, StatusCode
9
+
10
+ from fastmcp.telemetry import extract_trace_context, get_tracer
11
+
12
+
13
+ def get_auth_span_attributes() -> dict[str, str]:
14
+ """Get auth attributes for the current request, if authenticated."""
15
+ from fastmcp.server.dependencies import get_access_token
16
+
17
+ attrs: dict[str, str] = {}
18
+ try:
19
+ token = get_access_token()
20
+ if token:
21
+ if token.client_id:
22
+ attrs["enduser.id"] = token.client_id
23
+ if token.scopes:
24
+ attrs["enduser.scope"] = " ".join(token.scopes)
25
+ except RuntimeError:
26
+ pass
27
+ return attrs
28
+
29
+
30
+ def get_session_span_attributes() -> dict[str, str]:
31
+ """Get session attributes for the current request."""
32
+ from fastmcp.server.dependencies import get_context
33
+
34
+ attrs: dict[str, str] = {}
35
+ try:
36
+ ctx = get_context()
37
+ if ctx.request_context is not None and ctx.session_id is not None:
38
+ attrs["mcp.session.id"] = ctx.session_id
39
+ except RuntimeError:
40
+ pass
41
+ return attrs
42
+
43
+
44
+ def _get_parent_trace_context() -> Context | None:
45
+ """Get parent trace context from request meta for distributed tracing."""
46
+ try:
47
+ req_ctx = request_ctx.get()
48
+ if req_ctx and hasattr(req_ctx, "meta") and req_ctx.meta:
49
+ return extract_trace_context(dict(req_ctx.meta))
50
+ except LookupError:
51
+ pass
52
+ return None
53
+
54
+
55
+ @contextmanager
56
+ def server_span(
57
+ name: str,
58
+ method: str,
59
+ server_name: str,
60
+ component_type: str,
61
+ component_key: str,
62
+ resource_uri: str | None = None,
63
+ ) -> Generator[Span, None, None]:
64
+ """Create a SERVER span with standard MCP attributes and auth context.
65
+
66
+ Automatically records any exception on the span and sets error status.
67
+ """
68
+ tracer = get_tracer()
69
+ with tracer.start_as_current_span(
70
+ name,
71
+ context=_get_parent_trace_context(),
72
+ kind=SpanKind.SERVER,
73
+ ) as span:
74
+ attrs: dict[str, str] = {
75
+ # RPC semantic conventions
76
+ "rpc.system": "mcp",
77
+ "rpc.service": server_name,
78
+ "rpc.method": method,
79
+ # MCP semantic conventions
80
+ "mcp.method.name": method,
81
+ # FastMCP-specific attributes
82
+ "fastmcp.server.name": server_name,
83
+ "fastmcp.component.type": component_type,
84
+ "fastmcp.component.key": component_key,
85
+ **get_auth_span_attributes(),
86
+ **get_session_span_attributes(),
87
+ }
88
+ if resource_uri is not None:
89
+ attrs["mcp.resource.uri"] = resource_uri
90
+ span.set_attributes(attrs)
91
+ try:
92
+ yield span
93
+ except Exception as e:
94
+ span.record_exception(e)
95
+ span.set_status(Status(StatusCode.ERROR))
96
+ raise
97
+
98
+
99
+ @contextmanager
100
+ def delegate_span(
101
+ name: str,
102
+ provider_type: str,
103
+ component_key: str,
104
+ ) -> Generator[Span, None, None]:
105
+ """Create an INTERNAL span for provider delegation.
106
+
107
+ Used by FastMCPProvider when delegating to mounted servers.
108
+ Automatically records any exception on the span and sets error status.
109
+ """
110
+ tracer = get_tracer()
111
+ with tracer.start_as_current_span(f"delegate {name}") as span:
112
+ span.set_attributes(
113
+ {
114
+ "fastmcp.provider.type": provider_type,
115
+ "fastmcp.component.key": component_key,
116
+ }
117
+ )
118
+ try:
119
+ yield span
120
+ except Exception as e:
121
+ span.record_exception(e)
122
+ span.set_status(Status(StatusCode.ERROR))
123
+ raise
124
+
125
+
126
+ __all__ = [
127
+ "delegate_span",
128
+ "get_auth_span_attributes",
129
+ "get_session_span_attributes",
130
+ "server_span",
131
+ ]
@@ -0,0 +1,244 @@
1
+ """Transform system for component transformations.
2
+
3
+ Transforms modify components (tools, resources, prompts). List operations use a pure
4
+ function pattern where transforms receive sequences and return transformed sequences.
5
+ Get operations use a middleware pattern with `call_next` to chain lookups.
6
+
7
+ Unlike middleware (which operates on requests), transforms are observable by the
8
+ system for task registration, tag filtering, and component introspection.
9
+
10
+ Example:
11
+ ```python
12
+ from fastmcp import FastMCP
13
+ from fastmcp.server.transforms import Namespace
14
+
15
+ server = FastMCP("Server")
16
+ mount = server.mount(other_server)
17
+ mount.add_transform(Namespace("api")) # Tools become api_toolname
18
+ ```
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from collections.abc import Awaitable, Sequence
24
+ from typing import TYPE_CHECKING, Protocol
25
+
26
+ from fastmcp.utilities.versions import VersionSpec
27
+
28
+ if TYPE_CHECKING:
29
+ from fastmcp.prompts.prompt import Prompt
30
+ from fastmcp.resources.resource import Resource
31
+ from fastmcp.resources.template import ResourceTemplate
32
+ from fastmcp.tools.tool import Tool
33
+
34
+
35
+ # Get methods use Protocol to express keyword-only version parameter
36
+ class GetToolNext(Protocol):
37
+ """Protocol for get_tool call_next functions."""
38
+
39
+ def __call__(
40
+ self, name: str, *, version: VersionSpec | None = None
41
+ ) -> Awaitable[Tool | None]: ...
42
+
43
+
44
+ class GetResourceNext(Protocol):
45
+ """Protocol for get_resource call_next functions."""
46
+
47
+ def __call__(
48
+ self, uri: str, *, version: VersionSpec | None = None
49
+ ) -> Awaitable[Resource | None]: ...
50
+
51
+
52
+ class GetResourceTemplateNext(Protocol):
53
+ """Protocol for get_resource_template call_next functions."""
54
+
55
+ def __call__(
56
+ self, uri: str, *, version: VersionSpec | None = None
57
+ ) -> Awaitable[ResourceTemplate | None]: ...
58
+
59
+
60
+ class GetPromptNext(Protocol):
61
+ """Protocol for get_prompt call_next functions."""
62
+
63
+ def __call__(
64
+ self, name: str, *, version: VersionSpec | None = None
65
+ ) -> Awaitable[Prompt | None]: ...
66
+
67
+
68
+ class Transform:
69
+ """Base class for component transformations.
70
+
71
+ List operations use a pure function pattern: transforms receive sequences
72
+ and return transformed sequences. Get operations use a middleware pattern
73
+ with `call_next` to chain lookups.
74
+
75
+ Example:
76
+ ```python
77
+ class MyTransform(Transform):
78
+ async def list_tools(self, tools):
79
+ return [transform(t) for t in tools] # Transform sequence
80
+
81
+ async def get_tool(self, name, call_next, *, version=None):
82
+ original = self.reverse_name(name) # Map to original name
83
+ tool = await call_next(original, version=version) # Get from downstream
84
+ return transform(tool) if tool else None
85
+ ```
86
+ """
87
+
88
+ def __repr__(self) -> str:
89
+ return f"{self.__class__.__name__}()"
90
+
91
+ # -------------------------------------------------------------------------
92
+ # Tools
93
+ # -------------------------------------------------------------------------
94
+
95
+ async def list_tools(self, tools: Sequence[Tool]) -> Sequence[Tool]:
96
+ """List tools with transformation applied.
97
+
98
+ Args:
99
+ tools: Sequence of tools to transform.
100
+
101
+ Returns:
102
+ Transformed sequence of tools.
103
+ """
104
+ return tools
105
+
106
+ async def get_tool(
107
+ self, name: str, call_next: GetToolNext, *, version: VersionSpec | None = None
108
+ ) -> Tool | None:
109
+ """Get a tool by name.
110
+
111
+ Args:
112
+ name: The requested tool name (may be transformed).
113
+ call_next: Callable to get tool from downstream.
114
+ version: Optional version filter to apply.
115
+
116
+ Returns:
117
+ The tool if found, None otherwise.
118
+ """
119
+ return await call_next(name, version=version)
120
+
121
+ # -------------------------------------------------------------------------
122
+ # Resources
123
+ # -------------------------------------------------------------------------
124
+
125
+ async def list_resources(self, resources: Sequence[Resource]) -> Sequence[Resource]:
126
+ """List resources with transformation applied.
127
+
128
+ Args:
129
+ resources: Sequence of resources to transform.
130
+
131
+ Returns:
132
+ Transformed sequence of resources.
133
+ """
134
+ return resources
135
+
136
+ async def get_resource(
137
+ self,
138
+ uri: str,
139
+ call_next: GetResourceNext,
140
+ *,
141
+ version: VersionSpec | None = None,
142
+ ) -> Resource | None:
143
+ """Get a resource by URI.
144
+
145
+ Args:
146
+ uri: The requested resource URI (may be transformed).
147
+ call_next: Callable to get resource from downstream.
148
+ version: Optional version filter to apply.
149
+
150
+ Returns:
151
+ The resource if found, None otherwise.
152
+ """
153
+ return await call_next(uri, version=version)
154
+
155
+ # -------------------------------------------------------------------------
156
+ # Resource Templates
157
+ # -------------------------------------------------------------------------
158
+
159
+ async def list_resource_templates(
160
+ self, templates: Sequence[ResourceTemplate]
161
+ ) -> Sequence[ResourceTemplate]:
162
+ """List resource templates with transformation applied.
163
+
164
+ Args:
165
+ templates: Sequence of resource templates to transform.
166
+
167
+ Returns:
168
+ Transformed sequence of resource templates.
169
+ """
170
+ return templates
171
+
172
+ async def get_resource_template(
173
+ self,
174
+ uri: str,
175
+ call_next: GetResourceTemplateNext,
176
+ *,
177
+ version: VersionSpec | None = None,
178
+ ) -> ResourceTemplate | None:
179
+ """Get a resource template by URI.
180
+
181
+ Args:
182
+ uri: The requested template URI (may be transformed).
183
+ call_next: Callable to get template from downstream.
184
+ version: Optional version filter to apply.
185
+
186
+ Returns:
187
+ The resource template if found, None otherwise.
188
+ """
189
+ return await call_next(uri, version=version)
190
+
191
+ # -------------------------------------------------------------------------
192
+ # Prompts
193
+ # -------------------------------------------------------------------------
194
+
195
+ async def list_prompts(self, prompts: Sequence[Prompt]) -> Sequence[Prompt]:
196
+ """List prompts with transformation applied.
197
+
198
+ Args:
199
+ prompts: Sequence of prompts to transform.
200
+
201
+ Returns:
202
+ Transformed sequence of prompts.
203
+ """
204
+ return prompts
205
+
206
+ async def get_prompt(
207
+ self, name: str, call_next: GetPromptNext, *, version: VersionSpec | None = None
208
+ ) -> Prompt | None:
209
+ """Get a prompt by name.
210
+
211
+ Args:
212
+ name: The requested prompt name (may be transformed).
213
+ call_next: Callable to get prompt from downstream.
214
+ version: Optional version filter to apply.
215
+
216
+ Returns:
217
+ The prompt if found, None otherwise.
218
+ """
219
+ return await call_next(name, version=version)
220
+
221
+
222
+ # Re-export built-in transforms (must be after Transform class to avoid circular imports)
223
+ from fastmcp.server.transforms.visibility import Visibility, is_enabled # noqa: E402
224
+ from fastmcp.server.transforms.namespace import Namespace # noqa: E402
225
+ from fastmcp.server.transforms.prompts_as_tools import PromptsAsTools # noqa: E402
226
+ from fastmcp.server.transforms.resources_as_tools import ResourcesAsTools # noqa: E402
227
+ from fastmcp.server.transforms.tool_transform import ToolTransform # noqa: E402
228
+ from fastmcp.server.transforms.version_filter import VersionFilter # noqa: E402
229
+
230
+ __all__ = [
231
+ "GetPromptNext",
232
+ "GetResourceNext",
233
+ "GetResourceTemplateNext",
234
+ "GetToolNext",
235
+ "Namespace",
236
+ "PromptsAsTools",
237
+ "ResourcesAsTools",
238
+ "ToolTransform",
239
+ "Transform",
240
+ "VersionFilter",
241
+ "VersionSpec",
242
+ "Visibility",
243
+ "is_enabled",
244
+ ]
@@ -0,0 +1,193 @@
1
+ """Namespace transform for prefixing component names."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from collections.abc import Sequence
7
+ from typing import TYPE_CHECKING
8
+
9
+ from fastmcp.server.transforms import (
10
+ GetPromptNext,
11
+ GetResourceNext,
12
+ GetResourceTemplateNext,
13
+ GetToolNext,
14
+ Transform,
15
+ )
16
+ from fastmcp.utilities.versions import VersionSpec
17
+
18
+ if TYPE_CHECKING:
19
+ from fastmcp.prompts.prompt import Prompt
20
+ from fastmcp.resources.resource import Resource
21
+ from fastmcp.resources.template import ResourceTemplate
22
+ from fastmcp.tools.tool import Tool
23
+
24
+ # Pattern for matching URIs: protocol://path
25
+ _URI_PATTERN = re.compile(r"^([^:]+://)(.*?)$")
26
+
27
+
28
+ class Namespace(Transform):
29
+ """Prefixes component names with a namespace.
30
+
31
+ - Tools: name → namespace_name
32
+ - Prompts: name → namespace_name
33
+ - Resources: protocol://path → protocol://namespace/path
34
+ - Resource Templates: same as resources
35
+
36
+ Example:
37
+ ```python
38
+ transform = Namespace("math")
39
+ # Tool "add" becomes "math_add"
40
+ # Resource "file://data.txt" becomes "file://math/data.txt"
41
+ ```
42
+ """
43
+
44
+ def __init__(self, prefix: str) -> None:
45
+ """Initialize Namespace transform.
46
+
47
+ Args:
48
+ prefix: The namespace prefix to apply.
49
+ """
50
+ self._prefix = prefix
51
+ self._name_prefix = f"{prefix}_"
52
+
53
+ def __repr__(self) -> str:
54
+ return f"Namespace({self._prefix!r})"
55
+
56
+ # -------------------------------------------------------------------------
57
+ # Name transformation helpers
58
+ # -------------------------------------------------------------------------
59
+
60
+ def _transform_name(self, name: str) -> str:
61
+ """Apply namespace prefix to a name."""
62
+ return f"{self._name_prefix}{name}"
63
+
64
+ def _reverse_name(self, name: str) -> str | None:
65
+ """Remove namespace prefix from a name, or None if no match."""
66
+ if name.startswith(self._name_prefix):
67
+ return name[len(self._name_prefix) :]
68
+ return None
69
+
70
+ # -------------------------------------------------------------------------
71
+ # URI transformation helpers
72
+ # -------------------------------------------------------------------------
73
+
74
+ def _transform_uri(self, uri: str) -> str:
75
+ """Apply namespace to a URI: protocol://path → protocol://namespace/path."""
76
+ match = _URI_PATTERN.match(uri)
77
+ if match:
78
+ protocol, path = match.groups()
79
+ return f"{protocol}{self._prefix}/{path}"
80
+ return uri
81
+
82
+ def _reverse_uri(self, uri: str) -> str | None:
83
+ """Remove namespace from a URI, or None if no match."""
84
+ match = _URI_PATTERN.match(uri)
85
+ if match:
86
+ protocol, path = match.groups()
87
+ prefix = f"{self._prefix}/"
88
+ if path.startswith(prefix):
89
+ return f"{protocol}{path[len(prefix) :]}"
90
+ return None
91
+ return None
92
+
93
+ # -------------------------------------------------------------------------
94
+ # Tools
95
+ # -------------------------------------------------------------------------
96
+
97
+ async def list_tools(self, tools: Sequence[Tool]) -> Sequence[Tool]:
98
+ """Prefix tool names with namespace."""
99
+ return [
100
+ t.model_copy(update={"name": self._transform_name(t.name)}) for t in tools
101
+ ]
102
+
103
+ async def get_tool(
104
+ self, name: str, call_next: GetToolNext, *, version: VersionSpec | None = None
105
+ ) -> Tool | None:
106
+ """Get tool by namespaced name."""
107
+ original = self._reverse_name(name)
108
+ if original is None:
109
+ return None
110
+ tool = await call_next(original, version=version)
111
+ if tool:
112
+ return tool.model_copy(update={"name": name})
113
+ return None
114
+
115
+ # -------------------------------------------------------------------------
116
+ # Resources
117
+ # -------------------------------------------------------------------------
118
+
119
+ async def list_resources(self, resources: Sequence[Resource]) -> Sequence[Resource]:
120
+ """Add namespace path segment to resource URIs."""
121
+ return [
122
+ r.model_copy(update={"uri": self._transform_uri(str(r.uri))})
123
+ for r in resources
124
+ ]
125
+
126
+ async def get_resource(
127
+ self,
128
+ uri: str,
129
+ call_next: GetResourceNext,
130
+ *,
131
+ version: VersionSpec | None = None,
132
+ ) -> Resource | None:
133
+ """Get resource by namespaced URI."""
134
+ original = self._reverse_uri(uri)
135
+ if original is None:
136
+ return None
137
+ resource = await call_next(original, version=version)
138
+ if resource:
139
+ return resource.model_copy(update={"uri": uri})
140
+ return None
141
+
142
+ # -------------------------------------------------------------------------
143
+ # Resource Templates
144
+ # -------------------------------------------------------------------------
145
+
146
+ async def list_resource_templates(
147
+ self, templates: Sequence[ResourceTemplate]
148
+ ) -> Sequence[ResourceTemplate]:
149
+ """Add namespace path segment to template URIs."""
150
+ return [
151
+ t.model_copy(update={"uri_template": self._transform_uri(t.uri_template)})
152
+ for t in templates
153
+ ]
154
+
155
+ async def get_resource_template(
156
+ self,
157
+ uri: str,
158
+ call_next: GetResourceTemplateNext,
159
+ *,
160
+ version: VersionSpec | None = None,
161
+ ) -> ResourceTemplate | None:
162
+ """Get resource template by namespaced URI."""
163
+ original = self._reverse_uri(uri)
164
+ if original is None:
165
+ return None
166
+ template = await call_next(original, version=version)
167
+ if template:
168
+ return template.model_copy(
169
+ update={"uri_template": self._transform_uri(template.uri_template)}
170
+ )
171
+ return None
172
+
173
+ # -------------------------------------------------------------------------
174
+ # Prompts
175
+ # -------------------------------------------------------------------------
176
+
177
+ async def list_prompts(self, prompts: Sequence[Prompt]) -> Sequence[Prompt]:
178
+ """Prefix prompt names with namespace."""
179
+ return [
180
+ p.model_copy(update={"name": self._transform_name(p.name)}) for p in prompts
181
+ ]
182
+
183
+ async def get_prompt(
184
+ self, name: str, call_next: GetPromptNext, *, version: VersionSpec | None = None
185
+ ) -> Prompt | None:
186
+ """Get prompt by namespaced name."""
187
+ original = self._reverse_name(name)
188
+ if original is None:
189
+ return None
190
+ prompt = await call_next(original, version=version)
191
+ if prompt:
192
+ return prompt.model_copy(update={"name": name})
193
+ return None