fastmcp 2.12.5__py3-none-any.whl → 2.14.0__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 (133) hide show
  1. fastmcp/__init__.py +2 -23
  2. fastmcp/cli/__init__.py +0 -3
  3. fastmcp/cli/__main__.py +5 -0
  4. fastmcp/cli/cli.py +19 -33
  5. fastmcp/cli/install/claude_code.py +6 -6
  6. fastmcp/cli/install/claude_desktop.py +3 -3
  7. fastmcp/cli/install/cursor.py +18 -12
  8. fastmcp/cli/install/gemini_cli.py +3 -3
  9. fastmcp/cli/install/mcp_json.py +3 -3
  10. fastmcp/cli/install/shared.py +0 -15
  11. fastmcp/cli/run.py +13 -8
  12. fastmcp/cli/tasks.py +110 -0
  13. fastmcp/client/__init__.py +9 -9
  14. fastmcp/client/auth/oauth.py +123 -225
  15. fastmcp/client/client.py +697 -95
  16. fastmcp/client/elicitation.py +11 -5
  17. fastmcp/client/logging.py +18 -14
  18. fastmcp/client/messages.py +7 -5
  19. fastmcp/client/oauth_callback.py +85 -171
  20. fastmcp/client/roots.py +2 -1
  21. fastmcp/client/sampling.py +1 -1
  22. fastmcp/client/tasks.py +614 -0
  23. fastmcp/client/transports.py +117 -30
  24. fastmcp/contrib/component_manager/__init__.py +1 -1
  25. fastmcp/contrib/component_manager/component_manager.py +2 -2
  26. fastmcp/contrib/component_manager/component_service.py +10 -26
  27. fastmcp/contrib/mcp_mixin/README.md +32 -1
  28. fastmcp/contrib/mcp_mixin/__init__.py +2 -2
  29. fastmcp/contrib/mcp_mixin/mcp_mixin.py +14 -2
  30. fastmcp/dependencies.py +25 -0
  31. fastmcp/experimental/sampling/handlers/openai.py +3 -3
  32. fastmcp/experimental/server/openapi/__init__.py +20 -21
  33. fastmcp/experimental/utilities/openapi/__init__.py +16 -47
  34. fastmcp/mcp_config.py +3 -4
  35. fastmcp/prompts/__init__.py +1 -1
  36. fastmcp/prompts/prompt.py +54 -51
  37. fastmcp/prompts/prompt_manager.py +16 -101
  38. fastmcp/resources/__init__.py +5 -5
  39. fastmcp/resources/resource.py +43 -21
  40. fastmcp/resources/resource_manager.py +9 -168
  41. fastmcp/resources/template.py +161 -61
  42. fastmcp/resources/types.py +30 -24
  43. fastmcp/server/__init__.py +1 -1
  44. fastmcp/server/auth/__init__.py +9 -14
  45. fastmcp/server/auth/auth.py +197 -46
  46. fastmcp/server/auth/handlers/authorize.py +326 -0
  47. fastmcp/server/auth/jwt_issuer.py +236 -0
  48. fastmcp/server/auth/middleware.py +96 -0
  49. fastmcp/server/auth/oauth_proxy.py +1469 -298
  50. fastmcp/server/auth/oidc_proxy.py +91 -20
  51. fastmcp/server/auth/providers/auth0.py +40 -21
  52. fastmcp/server/auth/providers/aws.py +29 -3
  53. fastmcp/server/auth/providers/azure.py +312 -131
  54. fastmcp/server/auth/providers/debug.py +114 -0
  55. fastmcp/server/auth/providers/descope.py +86 -29
  56. fastmcp/server/auth/providers/discord.py +308 -0
  57. fastmcp/server/auth/providers/github.py +29 -8
  58. fastmcp/server/auth/providers/google.py +48 -9
  59. fastmcp/server/auth/providers/in_memory.py +29 -5
  60. fastmcp/server/auth/providers/introspection.py +281 -0
  61. fastmcp/server/auth/providers/jwt.py +48 -31
  62. fastmcp/server/auth/providers/oci.py +233 -0
  63. fastmcp/server/auth/providers/scalekit.py +238 -0
  64. fastmcp/server/auth/providers/supabase.py +188 -0
  65. fastmcp/server/auth/providers/workos.py +35 -17
  66. fastmcp/server/context.py +236 -116
  67. fastmcp/server/dependencies.py +503 -18
  68. fastmcp/server/elicitation.py +286 -48
  69. fastmcp/server/event_store.py +177 -0
  70. fastmcp/server/http.py +71 -20
  71. fastmcp/server/low_level.py +165 -2
  72. fastmcp/server/middleware/__init__.py +1 -1
  73. fastmcp/server/middleware/caching.py +476 -0
  74. fastmcp/server/middleware/error_handling.py +14 -10
  75. fastmcp/server/middleware/logging.py +50 -39
  76. fastmcp/server/middleware/middleware.py +29 -16
  77. fastmcp/server/middleware/rate_limiting.py +3 -3
  78. fastmcp/server/middleware/tool_injection.py +116 -0
  79. fastmcp/server/openapi/__init__.py +35 -0
  80. fastmcp/{experimental/server → server}/openapi/components.py +15 -10
  81. fastmcp/{experimental/server → server}/openapi/routing.py +3 -3
  82. fastmcp/{experimental/server → server}/openapi/server.py +6 -5
  83. fastmcp/server/proxy.py +72 -48
  84. fastmcp/server/server.py +1415 -733
  85. fastmcp/server/tasks/__init__.py +21 -0
  86. fastmcp/server/tasks/capabilities.py +22 -0
  87. fastmcp/server/tasks/config.py +89 -0
  88. fastmcp/server/tasks/converters.py +205 -0
  89. fastmcp/server/tasks/handlers.py +356 -0
  90. fastmcp/server/tasks/keys.py +93 -0
  91. fastmcp/server/tasks/protocol.py +355 -0
  92. fastmcp/server/tasks/subscriptions.py +205 -0
  93. fastmcp/settings.py +125 -113
  94. fastmcp/tools/__init__.py +1 -1
  95. fastmcp/tools/tool.py +138 -55
  96. fastmcp/tools/tool_manager.py +30 -112
  97. fastmcp/tools/tool_transform.py +12 -21
  98. fastmcp/utilities/cli.py +67 -28
  99. fastmcp/utilities/components.py +10 -5
  100. fastmcp/utilities/inspect.py +79 -23
  101. fastmcp/utilities/json_schema.py +4 -4
  102. fastmcp/utilities/json_schema_type.py +8 -8
  103. fastmcp/utilities/logging.py +118 -8
  104. fastmcp/utilities/mcp_config.py +1 -2
  105. fastmcp/utilities/mcp_server_config/__init__.py +3 -3
  106. fastmcp/utilities/mcp_server_config/v1/environments/base.py +1 -2
  107. fastmcp/utilities/mcp_server_config/v1/environments/uv.py +6 -6
  108. fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +5 -5
  109. fastmcp/utilities/mcp_server_config/v1/schema.json +3 -0
  110. fastmcp/utilities/mcp_server_config/v1/sources/base.py +0 -1
  111. fastmcp/{experimental/utilities → utilities}/openapi/README.md +7 -35
  112. fastmcp/utilities/openapi/__init__.py +63 -0
  113. fastmcp/{experimental/utilities → utilities}/openapi/director.py +14 -15
  114. fastmcp/{experimental/utilities → utilities}/openapi/formatters.py +5 -5
  115. fastmcp/{experimental/utilities → utilities}/openapi/json_schema_converter.py +7 -3
  116. fastmcp/{experimental/utilities → utilities}/openapi/parser.py +37 -16
  117. fastmcp/utilities/tests.py +92 -5
  118. fastmcp/utilities/types.py +86 -16
  119. fastmcp/utilities/ui.py +626 -0
  120. {fastmcp-2.12.5.dist-info → fastmcp-2.14.0.dist-info}/METADATA +24 -15
  121. fastmcp-2.14.0.dist-info/RECORD +156 -0
  122. {fastmcp-2.12.5.dist-info → fastmcp-2.14.0.dist-info}/WHEEL +1 -1
  123. fastmcp/cli/claude.py +0 -135
  124. fastmcp/server/auth/providers/bearer.py +0 -25
  125. fastmcp/server/openapi.py +0 -1083
  126. fastmcp/utilities/openapi.py +0 -1568
  127. fastmcp/utilities/storage.py +0 -204
  128. fastmcp-2.12.5.dist-info/RECORD +0 -134
  129. fastmcp/{experimental/server → server}/openapi/README.md +0 -0
  130. fastmcp/{experimental/utilities → utilities}/openapi/models.py +3 -3
  131. fastmcp/{experimental/utilities → utilities}/openapi/schemas.py +2 -2
  132. {fastmcp-2.12.5.dist-info → fastmcp-2.14.0.dist-info}/entry_points.txt +0 -0
  133. {fastmcp-2.12.5.dist-info → fastmcp-2.14.0.dist-info}/licenses/LICENSE +0 -0
@@ -2,13 +2,12 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import abc
6
5
  import inspect
7
6
  from collections.abc import Callable
8
7
  from typing import TYPE_CHECKING, Annotated, Any
9
8
 
10
9
  import pydantic_core
11
- from mcp.types import Annotations
10
+ from mcp.types import Annotations, Icon
12
11
  from mcp.types import Resource as MCPResource
13
12
  from pydantic import (
14
13
  AnyUrl,
@@ -20,10 +19,10 @@ from pydantic import (
20
19
  )
21
20
  from typing_extensions import Self
22
21
 
23
- from fastmcp.server.dependencies import get_context
22
+ from fastmcp.server.dependencies import get_context, without_injected_parameters
23
+ from fastmcp.server.tasks.config import TaskConfig
24
24
  from fastmcp.utilities.components import FastMCPComponent
25
25
  from fastmcp.utilities.types import (
26
- find_kwarg_by_type,
27
26
  get_fn_name,
28
27
  )
29
28
 
@@ -31,7 +30,7 @@ if TYPE_CHECKING:
31
30
  pass
32
31
 
33
32
 
34
- class Resource(FastMCPComponent, abc.ABC):
33
+ class Resource(FastMCPComponent):
35
34
  """Base class for all resources."""
36
35
 
37
36
  model_config = ConfigDict(validate_default=True)
@@ -43,7 +42,6 @@ class Resource(FastMCPComponent, abc.ABC):
43
42
  mime_type: str = Field(
44
43
  default="text/plain",
45
44
  description="MIME type of the resource content",
46
- pattern=r"^[a-zA-Z0-9]+/[a-zA-Z0-9\-+.]+$",
47
45
  )
48
46
  annotations: Annotated[
49
47
  Annotations | None,
@@ -73,11 +71,13 @@ class Resource(FastMCPComponent, abc.ABC):
73
71
  name: str | None = None,
74
72
  title: str | None = None,
75
73
  description: str | None = None,
74
+ icons: list[Icon] | None = None,
76
75
  mime_type: str | None = None,
77
76
  tags: set[str] | None = None,
78
77
  enabled: bool | None = None,
79
78
  annotations: Annotations | None = None,
80
79
  meta: dict[str, Any] | None = None,
80
+ task: bool | TaskConfig | None = None,
81
81
  ) -> FunctionResource:
82
82
  return FunctionResource.from_function(
83
83
  fn=fn,
@@ -85,11 +85,13 @@ class Resource(FastMCPComponent, abc.ABC):
85
85
  name=name,
86
86
  title=title,
87
87
  description=description,
88
+ icons=icons,
88
89
  mime_type=mime_type,
89
90
  tags=tags,
90
91
  enabled=enabled,
91
92
  annotations=annotations,
92
93
  meta=meta,
94
+ task=task,
93
95
  )
94
96
 
95
97
  @field_validator("mime_type", mode="before")
@@ -111,10 +113,13 @@ class Resource(FastMCPComponent, abc.ABC):
111
113
  raise ValueError("Either name or uri must be provided")
112
114
  return self
113
115
 
114
- @abc.abstractmethod
115
116
  async def read(self) -> str | bytes:
116
- """Read the resource content."""
117
- pass
117
+ """Read the resource content.
118
+
119
+ This method is not implemented in the base Resource class and must be
120
+ implemented by subclasses.
121
+ """
122
+ raise NotImplementedError("Subclasses must implement read()")
118
123
 
119
124
  def to_mcp_resource(
120
125
  self,
@@ -130,6 +135,7 @@ class Resource(FastMCPComponent, abc.ABC):
130
135
  description=overrides.get("description", self.description),
131
136
  mimeType=overrides.get("mimeType", self.mime_type),
132
137
  title=overrides.get("title", self.title),
138
+ icons=overrides.get("icons", self.icons),
133
139
  annotations=overrides.get("annotations", self.annotations),
134
140
  _meta=overrides.get(
135
141
  "_meta", self.get_meta(include_fastmcp_meta=include_fastmcp_meta)
@@ -164,6 +170,10 @@ class FunctionResource(Resource):
164
170
  """
165
171
 
166
172
  fn: Callable[..., Any]
173
+ task_config: Annotated[
174
+ TaskConfig,
175
+ Field(description="Background task execution configuration (SEP-1686)."),
176
+ ] = Field(default_factory=lambda: TaskConfig(mode="forbidden"))
167
177
 
168
178
  @classmethod
169
179
  def from_function(
@@ -173,46 +183,58 @@ class FunctionResource(Resource):
173
183
  name: str | None = None,
174
184
  title: str | None = None,
175
185
  description: str | None = None,
186
+ icons: list[Icon] | None = None,
176
187
  mime_type: str | None = None,
177
188
  tags: set[str] | None = None,
178
189
  enabled: bool | None = None,
179
190
  annotations: Annotations | None = None,
180
191
  meta: dict[str, Any] | None = None,
192
+ task: bool | TaskConfig | None = None,
181
193
  ) -> FunctionResource:
182
194
  """Create a FunctionResource from a function."""
183
195
  if isinstance(uri, str):
184
196
  uri = AnyUrl(uri)
197
+
198
+ func_name = name or get_fn_name(fn)
199
+
200
+ # Normalize task to TaskConfig and validate
201
+ if task is None:
202
+ task_config = TaskConfig(mode="forbidden")
203
+ elif isinstance(task, bool):
204
+ task_config = TaskConfig.from_bool(task)
205
+ else:
206
+ task_config = task
207
+ task_config.validate_function(fn, func_name)
208
+
209
+ # Wrap fn to handle dependency resolution internally
210
+ wrapped_fn = without_injected_parameters(fn)
211
+
185
212
  return cls(
186
- fn=fn,
213
+ fn=wrapped_fn,
187
214
  uri=uri,
188
215
  name=name or get_fn_name(fn),
189
216
  title=title,
190
217
  description=description or inspect.getdoc(fn),
218
+ icons=icons,
191
219
  mime_type=mime_type or "text/plain",
192
220
  tags=tags or set(),
193
221
  enabled=enabled if enabled is not None else True,
194
222
  annotations=annotations,
195
223
  meta=meta,
224
+ task_config=task_config,
196
225
  )
197
226
 
198
227
  async def read(self) -> str | bytes:
199
228
  """Read the resource by calling the wrapped function."""
200
- from fastmcp.server.context import Context
201
-
202
- kwargs = {}
203
- context_kwarg = find_kwarg_by_type(self.fn, kwarg_type=Context)
204
- if context_kwarg is not None:
205
- kwargs[context_kwarg] = get_context()
206
-
207
- result = self.fn(**kwargs)
229
+ # self.fn is wrapped by without_injected_parameters which handles
230
+ # dependency resolution internally
231
+ result = self.fn()
208
232
  if inspect.isawaitable(result):
209
233
  result = await result
210
234
 
211
235
  if isinstance(result, Resource):
212
236
  return await result.read()
213
- elif isinstance(result, bytes):
214
- return result
215
- elif isinstance(result, str):
237
+ elif isinstance(result, bytes | str):
216
238
  return result
217
239
  else:
218
240
  return pydantic_core.to_json(result, fallback=str).decode()
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import inspect
6
6
  import warnings
7
7
  from collections.abc import Callable
8
- from typing import TYPE_CHECKING, Any
8
+ from typing import Any
9
9
 
10
10
  from pydantic import AnyUrl
11
11
 
@@ -19,9 +19,6 @@ from fastmcp.resources.template import (
19
19
  from fastmcp.settings import DuplicateBehavior
20
20
  from fastmcp.utilities.logging import get_logger
21
21
 
22
- if TYPE_CHECKING:
23
- from fastmcp.server.server import MountedServer
24
-
25
22
  logger = get_logger(__name__)
26
23
 
27
24
 
@@ -43,7 +40,6 @@ class ResourceManager:
43
40
  """
44
41
  self._resources: dict[str, Resource] = {}
45
42
  self._templates: dict[str, ResourceTemplate] = {}
46
- self._mounted_servers: list[MountedServer] = []
47
43
  self.mask_error_details = mask_error_details or settings.mask_error_details
48
44
 
49
45
  # Default to "warn" if None is provided
@@ -57,137 +53,13 @@ class ResourceManager:
57
53
  )
58
54
  self.duplicate_behavior = duplicate_behavior
59
55
 
60
- def mount(self, server: MountedServer) -> None:
61
- """Adds a mounted server as a source for resources and templates."""
62
- self._mounted_servers.append(server)
63
-
64
56
  async def get_resources(self) -> dict[str, Resource]:
65
57
  """Get all registered resources, keyed by URI."""
66
- return await self._load_resources(via_server=False)
58
+ return dict(self._resources)
67
59
 
68
60
  async def get_resource_templates(self) -> dict[str, ResourceTemplate]:
69
61
  """Get all registered templates, keyed by URI template."""
70
- return await self._load_resource_templates(via_server=False)
71
-
72
- async def _load_resources(self, *, via_server: bool = False) -> dict[str, Resource]:
73
- """
74
- The single, consolidated recursive method for fetching resources. The 'via_server'
75
- parameter determines the communication path.
76
-
77
- - via_server=False: Manager-to-manager path for complete, unfiltered inventory
78
- - via_server=True: Server-to-server path for filtered MCP requests
79
- """
80
- all_resources: dict[str, Resource] = {}
81
-
82
- for mounted in self._mounted_servers:
83
- try:
84
- if via_server:
85
- # Use the server-to-server filtered path
86
- child_resources_list = await mounted.server._list_resources()
87
- child_resources = {
88
- resource.key: resource for resource in child_resources_list
89
- }
90
- else:
91
- # Use the manager-to-manager unfiltered path
92
- child_resources = (
93
- await mounted.server._resource_manager.get_resources()
94
- )
95
-
96
- # Apply prefix if needed
97
- if mounted.prefix:
98
- from fastmcp.server.server import add_resource_prefix
99
-
100
- for uri, resource in child_resources.items():
101
- prefixed_uri = add_resource_prefix(
102
- uri, mounted.prefix, mounted.resource_prefix_format
103
- )
104
- # Create a copy of the resource with the prefixed key and name
105
- prefixed_resource = resource.model_copy(
106
- update={"name": f"{mounted.prefix}_{resource.name}"},
107
- key=prefixed_uri,
108
- )
109
- all_resources[prefixed_uri] = prefixed_resource
110
- else:
111
- all_resources.update(child_resources)
112
- except Exception as e:
113
- # Skip failed mounts silently, matches existing behavior
114
- logger.warning(
115
- f"Failed to get resources from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}"
116
- )
117
- if settings.mounted_components_raise_on_load_error:
118
- raise
119
- continue
120
-
121
- # Finally, add local resources, which always take precedence
122
- all_resources.update(self._resources)
123
- return all_resources
124
-
125
- async def _load_resource_templates(
126
- self, *, via_server: bool = False
127
- ) -> dict[str, ResourceTemplate]:
128
- """
129
- The single, consolidated recursive method for fetching templates. The 'via_server'
130
- parameter determines the communication path.
131
-
132
- - via_server=False: Manager-to-manager path for complete, unfiltered inventory
133
- - via_server=True: Server-to-server path for filtered MCP requests
134
- """
135
- all_templates: dict[str, ResourceTemplate] = {}
136
-
137
- for mounted in self._mounted_servers:
138
- try:
139
- if via_server:
140
- # Use the server-to-server filtered path
141
- child_templates = await mounted.server._list_resource_templates()
142
- else:
143
- # Use the manager-to-manager unfiltered path
144
- child_templates = (
145
- await mounted.server._resource_manager.list_resource_templates()
146
- )
147
- child_dict = {template.key: template for template in child_templates}
148
-
149
- # Apply prefix if needed
150
- if mounted.prefix:
151
- from fastmcp.server.server import add_resource_prefix
152
-
153
- for uri_template, template in child_dict.items():
154
- prefixed_uri_template = add_resource_prefix(
155
- uri_template, mounted.prefix, mounted.resource_prefix_format
156
- )
157
- # Create a copy of the template with the prefixed key and name
158
- prefixed_template = template.model_copy(
159
- update={"name": f"{mounted.prefix}_{template.name}"},
160
- key=prefixed_uri_template,
161
- )
162
- all_templates[prefixed_uri_template] = prefixed_template
163
- else:
164
- all_templates.update(child_dict)
165
- except Exception as e:
166
- # Skip failed mounts silently, matches existing behavior
167
- logger.warning(
168
- f"Failed to get templates from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}"
169
- )
170
- if settings.mounted_components_raise_on_load_error:
171
- raise
172
- continue
173
-
174
- # Finally, add local templates, which always take precedence
175
- all_templates.update(self._templates)
176
- return all_templates
177
-
178
- async def list_resources(self) -> list[Resource]:
179
- """
180
- Lists all resources, applying protocol filtering.
181
- """
182
- resources_dict = await self._load_resources(via_server=True)
183
- return list(resources_dict.values())
184
-
185
- async def list_resource_templates(self) -> list[ResourceTemplate]:
186
- """
187
- Lists all templates, applying protocol filtering.
188
- """
189
- templates_dict = await self._load_resource_templates(via_server=True)
190
- return list(templates_dict.values())
62
+ return dict(self._templates)
191
63
 
192
64
  def add_resource_or_template_from_fn(
193
65
  self,
@@ -363,8 +235,8 @@ class ResourceManager:
363
235
 
364
236
  # Then check templates (local and mounted) only if not found in concrete resources
365
237
  templates = await self.get_resource_templates()
366
- for template_key in templates.keys():
367
- if match_uri_template(uri_str, template_key):
238
+ for template_key in templates:
239
+ if match_uri_template(uri_str, template_key) is not None:
368
240
  return True
369
241
 
370
242
  return False
@@ -381,16 +253,16 @@ class ResourceManager:
381
253
  uri_str = str(uri)
382
254
  logger.debug("Getting resource", extra={"uri": uri_str})
383
255
 
384
- # First check concrete resources (local and mounted)
256
+ # First check concrete resources
385
257
  resources = await self.get_resources()
386
258
  if resource := resources.get(uri_str):
387
259
  return resource
388
260
 
389
- # Then check templates (local and mounted) - use the utility function to match against storage keys
261
+ # Then check templates
390
262
  templates = await self.get_resource_templates()
391
263
  for storage_key, template in templates.items():
392
264
  # Try to match against the storage key (which might be a custom key)
393
- if params := match_uri_template(uri_str, storage_key):
265
+ if (params := match_uri_template(uri_str, storage_key)) is not None:
394
266
  try:
395
267
  return await template.create_resource(
396
268
  uri_str,
@@ -424,9 +296,6 @@ class ResourceManager:
424
296
  # 1. Check local resources first. The server will have already applied its filter.
425
297
  if uri_str in self._resources:
426
298
  resource = await self.get_resource(uri_str)
427
- if not resource:
428
- raise NotFoundError(f"Resource {uri_str!r} not found")
429
-
430
299
  try:
431
300
  return await resource.read()
432
301
 
@@ -449,7 +318,7 @@ class ResourceManager:
449
318
 
450
319
  # 1b. Check local templates if not found in concrete resources
451
320
  for key, template in self._templates.items():
452
- if params := match_uri_template(uri_str, key):
321
+ if (params := match_uri_template(uri_str, key)) is not None:
453
322
  try:
454
323
  resource = await template.create_resource(uri_str, params=params)
455
324
  return await resource.read()
@@ -471,32 +340,4 @@ class ResourceManager:
471
340
  f"Error reading resource from template {uri_str!r}: {e}"
472
341
  ) from e
473
342
 
474
- # 2. Check mounted servers using the filtered protocol path.
475
- from fastmcp.server.server import has_resource_prefix, remove_resource_prefix
476
-
477
- for mounted in reversed(self._mounted_servers):
478
- key = uri_str
479
- try:
480
- if mounted.prefix:
481
- if has_resource_prefix(
482
- key,
483
- mounted.prefix,
484
- mounted.resource_prefix_format,
485
- ):
486
- key = remove_resource_prefix(
487
- key,
488
- mounted.prefix,
489
- mounted.resource_prefix_format,
490
- )
491
- else:
492
- continue
493
-
494
- try:
495
- result = await mounted.server._read_resource(key)
496
- return result[0].content
497
- except NotFoundError:
498
- continue
499
- except NotFoundError:
500
- continue
501
-
502
343
  raise NotFoundError(f"Resource {uri_str!r} not found.")