fastmcp 2.7.1__py3-none-any.whl → 2.8.1__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.
@@ -5,12 +5,11 @@ from __future__ import annotations
5
5
  import inspect
6
6
  import re
7
7
  from collections.abc import Callable
8
- from typing import Annotated, Any
8
+ from typing import Any
9
9
  from urllib.parse import unquote
10
10
 
11
11
  from mcp.types import ResourceTemplate as MCPResourceTemplate
12
12
  from pydantic import (
13
- BeforeValidator,
14
13
  Field,
15
14
  field_validator,
16
15
  validate_call,
@@ -18,10 +17,9 @@ from pydantic import (
18
17
 
19
18
  from fastmcp.resources.types import Resource
20
19
  from fastmcp.server.dependencies import get_context
20
+ from fastmcp.utilities.components import FastMCPComponent
21
21
  from fastmcp.utilities.json_schema import compress_schema
22
22
  from fastmcp.utilities.types import (
23
- FastMCPBaseModel,
24
- _convert_set_defaults,
25
23
  find_kwarg_by_type,
26
24
  get_cached_typeadapter,
27
25
  )
@@ -51,17 +49,12 @@ def match_uri_template(uri: str, uri_template: str) -> dict[str, str] | None:
51
49
  return None
52
50
 
53
51
 
54
- class ResourceTemplate(FastMCPBaseModel):
52
+ class ResourceTemplate(FastMCPComponent):
55
53
  """A template for dynamically creating resources."""
56
54
 
57
55
  uri_template: str = Field(
58
56
  description="URI template with parameters (e.g. weather://{city}/current)"
59
57
  )
60
- name: str = Field(description="Name of the resource")
61
- description: str | None = Field(description="Description of what the resource does")
62
- tags: Annotated[set[str], BeforeValidator(_convert_set_defaults)] = Field(
63
- default_factory=set, description="Tags for the resource"
64
- )
65
58
  mime_type: str = Field(
66
59
  default="text/plain", description="MIME type of the resource content"
67
60
  )
@@ -77,6 +70,7 @@ class ResourceTemplate(FastMCPBaseModel):
77
70
  description: str | None = None,
78
71
  mime_type: str | None = None,
79
72
  tags: set[str] | None = None,
73
+ enabled: bool | None = None,
80
74
  ) -> FunctionResourceTemplate:
81
75
  return FunctionResourceTemplate.from_function(
82
76
  fn=fn,
@@ -85,6 +79,7 @@ class ResourceTemplate(FastMCPBaseModel):
85
79
  description=description,
86
80
  mime_type=mime_type,
87
81
  tags=tags,
82
+ enabled=enabled,
88
83
  )
89
84
 
90
85
  @field_validator("mime_type", mode="before")
@@ -120,14 +115,9 @@ class ResourceTemplate(FastMCPBaseModel):
120
115
  description=self.description,
121
116
  mime_type=self.mime_type,
122
117
  tags=self.tags,
118
+ enabled=self.enabled,
123
119
  )
124
120
 
125
- def __eq__(self, other: object) -> bool:
126
- if type(self) is not type(other):
127
- return False
128
- assert isinstance(other, type(self))
129
- return self.model_dump() == other.model_dump()
130
-
131
121
  def to_mcp_template(self, **overrides: Any) -> MCPResourceTemplate:
132
122
  """Convert the resource template to an MCPResourceTemplate."""
133
123
  kwargs = {
@@ -168,6 +158,7 @@ class FunctionResourceTemplate(ResourceTemplate):
168
158
  description: str | None = None,
169
159
  mime_type: str | None = None,
170
160
  tags: set[str] | None = None,
161
+ enabled: bool | None = None,
171
162
  ) -> FunctionResourceTemplate:
172
163
  """Create a template from a function."""
173
164
  from fastmcp.server.context import Context
@@ -250,4 +241,5 @@ class FunctionResourceTemplate(ResourceTemplate):
250
241
  fn=fn,
251
242
  parameters=parameters,
252
243
  tags=tags or set(),
244
+ enabled=enabled if enabled is not None else True,
253
245
  )
@@ -1,13 +1,10 @@
1
+ from types import EllipsisType
2
+
1
3
  from pydantic_settings import BaseSettings, SettingsConfigDict
2
4
 
3
5
  from fastmcp.server.auth.providers.bearer import BearerAuthProvider
4
6
 
5
7
 
6
- # Sentinel object to indicate that a setting is not set
7
- class _NotSet:
8
- pass
9
-
10
-
11
8
  class EnvBearerAuthProviderSettings(BaseSettings):
12
9
  """Settings for the BearerAuthProvider."""
13
10
 
@@ -33,11 +30,11 @@ class EnvBearerAuthProvider(BearerAuthProvider):
33
30
 
34
31
  def __init__(
35
32
  self,
36
- public_key: str | None | type[_NotSet] = _NotSet,
37
- jwks_uri: str | None | type[_NotSet] = _NotSet,
38
- issuer: str | None | type[_NotSet] = _NotSet,
39
- audience: str | None | type[_NotSet] = _NotSet,
40
- required_scopes: list[str] | None | type[_NotSet] = _NotSet,
33
+ public_key: str | None | EllipsisType = ...,
34
+ jwks_uri: str | None | EllipsisType = ...,
35
+ issuer: str | None | EllipsisType = ...,
36
+ audience: str | None | EllipsisType = ...,
37
+ required_scopes: list[str] | None | EllipsisType = ...,
41
38
  ):
42
39
  """
43
40
  Initialize the provider.
@@ -57,6 +54,6 @@ class EnvBearerAuthProvider(BearerAuthProvider):
57
54
  "required_scopes": required_scopes,
58
55
  }
59
56
  settings = EnvBearerAuthProviderSettings(
60
- **{k: v for k, v in kwargs.items() if v is not _NotSet}
57
+ **{k: v for k, v in kwargs.items() if v is not ...}
61
58
  )
62
59
  super().__init__(**settings.model_dump())
@@ -184,7 +184,7 @@ class InMemoryOAuthProvider(OAuthProvider):
184
184
 
185
185
  return OAuthToken(
186
186
  access_token=access_token_value,
187
- token_type="bearer",
187
+ token_type="Bearer",
188
188
  expires_in=DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS,
189
189
  refresh_token=refresh_token_value,
190
190
  scope=" ".join(authorization_code.scopes),
@@ -254,7 +254,7 @@ class InMemoryOAuthProvider(OAuthProvider):
254
254
 
255
255
  return OAuthToken(
256
256
  access_token=new_access_token_value,
257
- token_type="bearer",
257
+ token_type="Bearer",
258
258
  expires_in=DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS,
259
259
  refresh_token=new_refresh_token_value,
260
260
  scope=" ".join(scopes),
fastmcp/server/context.py CHANGED
@@ -11,7 +11,6 @@ from mcp.server.lowlevel.helper_types import ReadResourceContents
11
11
  from mcp.shared.context import RequestContext
12
12
  from mcp.types import (
13
13
  CreateMessageResult,
14
- ImageContent,
15
14
  ModelHint,
16
15
  ModelPreferences,
17
16
  Root,
@@ -22,8 +21,10 @@ from pydantic.networks import AnyUrl
22
21
  from starlette.requests import Request
23
22
 
24
23
  import fastmcp.server.dependencies
24
+ from fastmcp import settings
25
25
  from fastmcp.server.server import FastMCP
26
26
  from fastmcp.utilities.logging import get_logger
27
+ from fastmcp.utilities.types import MCPContent
27
28
 
28
29
  logger = get_logger(__name__)
29
30
 
@@ -203,7 +204,7 @@ class Context:
203
204
  temperature: float | None = None,
204
205
  max_tokens: int | None = None,
205
206
  model_preferences: ModelPreferences | str | list[str] | None = None,
206
- ) -> TextContent | ImageContent:
207
+ ) -> MCPContent:
207
208
  """
208
209
  Send a sampling request to the client and await the response.
209
210
 
@@ -242,14 +243,15 @@ class Context:
242
243
  def get_http_request(self) -> Request:
243
244
  """Get the active starlette request."""
244
245
 
245
- # Deprecation warning, added in FastMCP 2.2.11
246
- warnings.warn(
247
- "Context.get_http_request() is deprecated and will be removed in a future version. "
248
- "Use get_http_request() from fastmcp.server.dependencies instead. "
249
- "See https://gofastmcp.com/patterns/http-requests for more details.",
250
- DeprecationWarning,
251
- stacklevel=2,
252
- )
246
+ # Deprecated in 2.2.11
247
+ if settings.deprecation_warnings:
248
+ warnings.warn(
249
+ "Context.get_http_request() is deprecated and will be removed in a future version. "
250
+ "Use get_http_request() from fastmcp.server.dependencies instead. "
251
+ "See https://gofastmcp.com/patterns/http-requests for more details.",
252
+ DeprecationWarning,
253
+ stacklevel=2,
254
+ )
253
255
 
254
256
  return fastmcp.server.dependencies.get_http_request()
255
257
 
fastmcp/server/openapi.py CHANGED
@@ -13,9 +13,10 @@ from re import Pattern
13
13
  from typing import TYPE_CHECKING, Any, Literal
14
14
 
15
15
  import httpx
16
- from mcp.types import EmbeddedResource, ImageContent, TextContent, ToolAnnotations
16
+ from mcp.types import ToolAnnotations
17
17
  from pydantic.networks import AnyUrl
18
18
 
19
+ import fastmcp
19
20
  from fastmcp.exceptions import ToolError
20
21
  from fastmcp.resources import Resource, ResourceTemplate
21
22
  from fastmcp.server.dependencies import get_http_headers
@@ -28,6 +29,7 @@ from fastmcp.utilities.openapi import (
28
29
  _combine_schemas,
29
30
  format_description_with_responses,
30
31
  )
32
+ from fastmcp.utilities.types import MCPContent
31
33
 
32
34
  if TYPE_CHECKING:
33
35
  from fastmcp.server import Context
@@ -103,41 +105,56 @@ class RouteType(enum.Enum):
103
105
  IGNORE = "IGNORE"
104
106
 
105
107
 
106
- @dataclass
108
+ @dataclass(kw_only=True)
107
109
  class RouteMap:
108
110
  """Mapping configuration for HTTP routes to FastMCP component types."""
109
111
 
110
112
  methods: list[HttpMethod] | Literal["*"] = field(default="*")
111
113
  pattern: Pattern[str] | str = field(default=r".*")
112
- mcp_type: MCPType | None = field(default=None)
113
114
  route_type: RouteType | MCPType | None = field(default=None)
114
- tags: set[str] = field(default_factory=set)
115
+ tags: set[str] = field(
116
+ default_factory=set,
117
+ metadata={"description": "A set of tags to match. All tags must match."},
118
+ )
119
+ mcp_type: MCPType | None = field(
120
+ default=None,
121
+ metadata={"description": "The type of FastMCP component to create."},
122
+ )
123
+ mcp_tags: set[str] = field(
124
+ default_factory=set,
125
+ metadata={
126
+ "description": "A set of tags to apply to the generated FastMCP component."
127
+ },
128
+ )
115
129
 
116
130
  def __post_init__(self):
117
131
  """Validate and process the route map after initialization."""
118
132
  # Handle backward compatibility for route_type, deprecated in 2.5.0
119
133
  if self.mcp_type is None and self.route_type is not None:
120
- warnings.warn(
121
- "The 'route_type' parameter is deprecated and will be removed in a future version. "
122
- "Use 'mcp_type' instead with the appropriate MCPType value.",
123
- DeprecationWarning,
124
- stacklevel=2,
125
- )
126
- if isinstance(self.route_type, RouteType):
134
+ if fastmcp.settings.deprecation_warnings:
127
135
  warnings.warn(
128
- "The RouteType class is deprecated and will be removed in a future version. "
129
- "Use MCPType instead.",
136
+ "The 'route_type' parameter is deprecated and will be removed in a future version. "
137
+ "Use 'mcp_type' instead with the appropriate MCPType value.",
130
138
  DeprecationWarning,
131
139
  stacklevel=2,
132
140
  )
141
+ if isinstance(self.route_type, RouteType):
142
+ if fastmcp.settings.deprecation_warnings:
143
+ warnings.warn(
144
+ "The RouteType class is deprecated and will be removed in a future version. "
145
+ "Use MCPType instead.",
146
+ DeprecationWarning,
147
+ stacklevel=2,
148
+ )
133
149
  # Check for the deprecated IGNORE value
134
150
  if self.route_type == RouteType.IGNORE:
135
- warnings.warn(
136
- "RouteType.IGNORE is deprecated and will be removed in a future version. "
137
- "Use MCPType.EXCLUDE instead.",
138
- DeprecationWarning,
139
- stacklevel=2,
140
- )
151
+ if fastmcp.settings.deprecation_warnings:
152
+ warnings.warn(
153
+ "RouteType.IGNORE is deprecated and will be removed in a future version. "
154
+ "Use MCPType.EXCLUDE instead.",
155
+ DeprecationWarning,
156
+ stacklevel=2,
157
+ )
141
158
 
142
159
  # Convert from RouteType to MCPType if needed
143
160
  if isinstance(self.route_type, RouteType):
@@ -155,23 +172,17 @@ class RouteMap:
155
172
  self.route_type = self.mcp_type
156
173
 
157
174
 
158
- # Default route mappings as a list, where order determines priority
175
+ # Default route mapping: all routes become tools.
176
+ # Users can provide custom route_maps to override this behavior.
159
177
  DEFAULT_ROUTE_MAPPINGS = [
160
- # GET requests with path parameters go to ResourceTemplate
161
- RouteMap(
162
- methods=["GET"], pattern=r".*\{.*\}.*", mcp_type=MCPType.RESOURCE_TEMPLATE
163
- ),
164
- # GET requests without path parameters go to Resource
165
- RouteMap(methods=["GET"], pattern=r".*", mcp_type=MCPType.RESOURCE),
166
- # All other HTTP methods go to Tool
167
- RouteMap(methods="*", pattern=r".*", mcp_type=MCPType.TOOL),
178
+ RouteMap(mcp_type=MCPType.TOOL),
168
179
  ]
169
180
 
170
181
 
171
182
  def _determine_route_type(
172
183
  route: openapi.HTTPRoute,
173
184
  mappings: list[RouteMap],
174
- ) -> MCPType:
185
+ ) -> RouteMap:
175
186
  """
176
187
  Determines the FastMCP component type based on the route and mappings.
177
188
 
@@ -180,7 +191,7 @@ def _determine_route_type(
180
191
  mappings: List of RouteMap objects in priority order
181
192
 
182
193
  Returns:
183
- MCPType for this route
194
+ The RouteMap that matches the route, or a catchall "Tool" RouteMap if no match is found.
184
195
  """
185
196
  # Check mappings in priority order (first match wins)
186
197
  for route_map in mappings:
@@ -207,10 +218,10 @@ def _determine_route_type(
207
218
  logger.debug(
208
219
  f"Route {route.method} {route.path} matched mapping to {route_map.mcp_type.name}"
209
220
  )
210
- return route_map.mcp_type
221
+ return route_map
211
222
 
212
223
  # Default fallback
213
- return MCPType.TOOL
224
+ return RouteMap(mcp_type=MCPType.TOOL)
214
225
 
215
226
 
216
227
  class OpenAPITool(Tool):
@@ -223,19 +234,17 @@ class OpenAPITool(Tool):
223
234
  name: str,
224
235
  description: str,
225
236
  parameters: dict[str, Any],
226
- tags: set[str] = set(),
237
+ tags: set[str] | None = None,
227
238
  timeout: float | None = None,
228
239
  annotations: ToolAnnotations | None = None,
229
- exclude_args: list[str] | None = None,
230
240
  serializer: Callable[[Any], str] | None = None,
231
241
  ):
232
242
  super().__init__(
233
243
  name=name,
234
244
  description=description,
235
245
  parameters=parameters,
236
- tags=tags,
246
+ tags=tags or set(),
237
247
  annotations=annotations,
238
- exclude_args=exclude_args,
239
248
  serializer=serializer,
240
249
  )
241
250
  self._client = client
@@ -246,9 +255,7 @@ class OpenAPITool(Tool):
246
255
  """Custom representation to prevent recursion errors when printing."""
247
256
  return f"OpenAPITool(name={self.name!r}, method={self._route.method}, path={self._route.path})"
248
257
 
249
- async def run(
250
- self, arguments: dict[str, Any]
251
- ) -> list[TextContent | ImageContent | EmbeddedResource]:
258
+ async def run(self, arguments: dict[str, Any]) -> list[MCPContent]:
252
259
  """Execute the HTTP request based on the route configuration."""
253
260
 
254
261
  # Prepare URL
@@ -688,6 +695,7 @@ class FastMCPOpenAPI(FastMCP):
688
695
  route_map_fn: RouteMapFn | None = None,
689
696
  mcp_component_fn: ComponentFn | None = None,
690
697
  mcp_names: dict[str, str] | None = None,
698
+ tags: set[str] | None = None,
691
699
  timeout: float | None = None,
692
700
  **settings: Any,
693
701
  ):
@@ -710,6 +718,8 @@ class FastMCPOpenAPI(FastMCP):
710
718
  operationId up to the first double underscore. If no operationId exists,
711
719
  falls back to slugified summary or path-based naming.
712
720
  All names are truncated to 56 characters maximum.
721
+ tags: Optional set of tags to add to all components. Components always receive any tags
722
+ from the route.
713
723
  timeout: Optional timeout (in seconds) for all requests
714
724
  **settings: Additional settings for FastMCP
715
725
  """
@@ -717,9 +727,7 @@ class FastMCPOpenAPI(FastMCP):
717
727
 
718
728
  self._client = client
719
729
  self._timeout = timeout
720
- self._route_map_fn = route_map_fn
721
730
  self._mcp_component_fn = mcp_component_fn
722
- self._mcp_names = mcp_names or {}
723
731
 
724
732
  # Keep track of names to detect collisions
725
733
  self._used_names = {
@@ -735,12 +743,16 @@ class FastMCPOpenAPI(FastMCP):
735
743
  route_maps = (route_maps or []) + DEFAULT_ROUTE_MAPPINGS
736
744
  for route in http_routes:
737
745
  # Determine route type based on mappings or default rules
738
- route_type = _determine_route_type(route, route_maps)
746
+ route_map = _determine_route_type(route, route_maps)
747
+
748
+ # TODO: remove this once RouteType is removed and mcp_type is typed as MCPType without | None
749
+ assert route_map.mcp_type is not None
750
+ route_type = route_map.mcp_type
739
751
 
740
752
  # Call route_map_fn if provided
741
- if self._route_map_fn is not None:
753
+ if route_map_fn is not None:
742
754
  try:
743
- result = self._route_map_fn(route, route_type)
755
+ result = route_map_fn(route, route_type)
744
756
  if result is not None:
745
757
  route_type = result
746
758
  logger.debug(
@@ -754,29 +766,32 @@ class FastMCPOpenAPI(FastMCP):
754
766
  )
755
767
 
756
768
  # Generate a default name from the route
757
- component_name = self._generate_default_name(route, route_type)
769
+ component_name = self._generate_default_name(route, mcp_names)
770
+
771
+ route_tags = set(route.tags) | route_map.mcp_tags | (tags or set())
758
772
 
759
773
  if route_type == MCPType.TOOL:
760
- self._create_openapi_tool(route, component_name)
774
+ self._create_openapi_tool(route, component_name, tags=route_tags)
761
775
  elif route_type == MCPType.RESOURCE:
762
- self._create_openapi_resource(route, component_name)
776
+ self._create_openapi_resource(route, component_name, tags=route_tags)
763
777
  elif route_type == MCPType.RESOURCE_TEMPLATE:
764
- self._create_openapi_template(route, component_name)
778
+ self._create_openapi_template(route, component_name, tags=route_tags)
765
779
  elif route_type == MCPType.EXCLUDE:
766
780
  logger.info(f"Excluding route: {route.method} {route.path}")
767
781
 
768
782
  logger.info(f"Created FastMCP OpenAPI server with {len(http_routes)} routes")
769
783
 
770
784
  def _generate_default_name(
771
- self, route: openapi.HTTPRoute, mcp_type: MCPType
785
+ self, route: openapi.HTTPRoute, mcp_names_map: dict[str, str] | None = None
772
786
  ) -> str:
773
787
  """Generate a default name from the route using the configured strategy."""
774
788
  name = ""
789
+ mcp_names_map = mcp_names_map or {}
775
790
 
776
791
  # First check if there's a custom mapping for this operationId
777
792
  if route.operation_id:
778
- if route.operation_id in self._mcp_names:
779
- name = self._mcp_names[route.operation_id]
793
+ if route.operation_id in mcp_names_map:
794
+ name = mcp_names_map[route.operation_id]
780
795
  else:
781
796
  # If there's a double underscore in the operationId, use the first part
782
797
  name = route.operation_id.split("__")[0]
@@ -821,7 +836,12 @@ class FastMCPOpenAPI(FastMCP):
821
836
 
822
837
  return new_name
823
838
 
824
- def _create_openapi_tool(self, route: openapi.HTTPRoute, name: str):
839
+ def _create_openapi_tool(
840
+ self,
841
+ route: openapi.HTTPRoute,
842
+ name: str,
843
+ tags: set[str],
844
+ ):
825
845
  """Creates and registers an OpenAPITool with enhanced description."""
826
846
  combined_schema = _combine_schemas(route)
827
847
 
@@ -848,7 +868,7 @@ class FastMCPOpenAPI(FastMCP):
848
868
  name=tool_name,
849
869
  description=enhanced_description,
850
870
  parameters=combined_schema,
851
- tags=set(route.tags or []),
871
+ tags=set(route.tags or []) | tags,
852
872
  timeout=self._timeout,
853
873
  )
854
874
 
@@ -869,7 +889,12 @@ class FastMCPOpenAPI(FastMCP):
869
889
  f"Registered TOOL: {tool_name} ({route.method} {route.path}) with tags: {route.tags}"
870
890
  )
871
891
 
872
- def _create_openapi_resource(self, route: openapi.HTTPRoute, name: str):
892
+ def _create_openapi_resource(
893
+ self,
894
+ route: openapi.HTTPRoute,
895
+ name: str,
896
+ tags: set[str],
897
+ ):
873
898
  """Creates and registers an OpenAPIResource with enhanced description."""
874
899
  # Get a unique resource name
875
900
  resource_name = self._get_unique_name(name, "resource")
@@ -893,7 +918,7 @@ class FastMCPOpenAPI(FastMCP):
893
918
  uri=resource_uri,
894
919
  name=resource_name,
895
920
  description=enhanced_description,
896
- tags=set(route.tags or []),
921
+ tags=set(route.tags or []) | tags,
897
922
  timeout=self._timeout,
898
923
  )
899
924
 
@@ -914,7 +939,12 @@ class FastMCPOpenAPI(FastMCP):
914
939
  f"Registered RESOURCE: {resource_uri} ({route.method} {route.path}) with tags: {route.tags}"
915
940
  )
916
941
 
917
- def _create_openapi_template(self, route: openapi.HTTPRoute, name: str):
942
+ def _create_openapi_template(
943
+ self,
944
+ route: openapi.HTTPRoute,
945
+ name: str,
946
+ tags: set[str],
947
+ ):
918
948
  """Creates and registers an OpenAPIResourceTemplate with enhanced description."""
919
949
  # Get a unique template name
920
950
  template_name = self._get_unique_name(name, "resource_template")
@@ -967,7 +997,7 @@ class FastMCPOpenAPI(FastMCP):
967
997
  name=template_name,
968
998
  description=enhanced_description,
969
999
  parameters=template_params_schema,
970
- tags=set(route.tags or []),
1000
+ tags=set(route.tags or []) | tags,
971
1001
  timeout=self._timeout,
972
1002
  )
973
1003
 
fastmcp/server/proxy.py CHANGED
@@ -9,10 +9,7 @@ from mcp.shared.exceptions import McpError
9
9
  from mcp.types import (
10
10
  METHOD_NOT_FOUND,
11
11
  BlobResourceContents,
12
- EmbeddedResource,
13
12
  GetPromptResult,
14
- ImageContent,
15
- TextContent,
16
13
  TextResourceContents,
17
14
  )
18
15
  from pydantic.networks import AnyUrl
@@ -25,6 +22,7 @@ from fastmcp.server.context import Context
25
22
  from fastmcp.server.server import FastMCP
26
23
  from fastmcp.tools.tool import Tool
27
24
  from fastmcp.utilities.logging import get_logger
25
+ from fastmcp.utilities.types import MCPContent
28
26
 
29
27
  if TYPE_CHECKING:
30
28
  from fastmcp.server import Context
@@ -50,7 +48,7 @@ class ProxyTool(Tool):
50
48
  self,
51
49
  arguments: dict[str, Any],
52
50
  context: Context | None = None,
53
- ) -> list[TextContent | ImageContent | EmbeddedResource]:
51
+ ) -> list[MCPContent]:
54
52
  # the client context manager will swallow any exceptions inside a TaskGroup
55
53
  # so we return the raw result and raise an exception ourselves
56
54
  async with self._client:
@@ -186,8 +184,10 @@ class FastMCPProxy(FastMCP):
186
184
  else:
187
185
  raise e
188
186
  for tool in client_tools:
189
- tool_proxy = await ProxyTool.from_client(self.client, tool)
190
- tools[tool_proxy.name] = tool_proxy
187
+ # don't overwrite tools defined in the server
188
+ if tool.name not in tools:
189
+ tool_proxy = await ProxyTool.from_client(self.client, tool)
190
+ tools[tool_proxy.name] = tool_proxy
191
191
 
192
192
  return tools
193
193
 
@@ -203,8 +203,12 @@ class FastMCPProxy(FastMCP):
203
203
  else:
204
204
  raise e
205
205
  for resource in client_resources:
206
- resource_proxy = await ProxyResource.from_client(self.client, resource)
207
- resources[str(resource_proxy.uri)] = resource_proxy
206
+ # don't overwrite resources defined in the server
207
+ if str(resource.uri) not in resources:
208
+ resource_proxy = await ProxyResource.from_client(
209
+ self.client, resource
210
+ )
211
+ resources[str(resource_proxy.uri)] = resource_proxy
208
212
 
209
213
  return resources
210
214
 
@@ -220,8 +224,12 @@ class FastMCPProxy(FastMCP):
220
224
  else:
221
225
  raise e
222
226
  for template in client_templates:
223
- template_proxy = await ProxyTemplate.from_client(self.client, template)
224
- templates[template_proxy.uri_template] = template_proxy
227
+ # don't overwrite templates defined in the server
228
+ if template.uriTemplate not in templates:
229
+ template_proxy = await ProxyTemplate.from_client(
230
+ self.client, template
231
+ )
232
+ templates[template_proxy.uri_template] = template_proxy
225
233
 
226
234
  return templates
227
235
 
@@ -237,24 +245,25 @@ class FastMCPProxy(FastMCP):
237
245
  else:
238
246
  raise e
239
247
  for prompt in client_prompts:
240
- prompt_proxy = await ProxyPrompt.from_client(self.client, prompt)
241
- prompts[prompt_proxy.name] = prompt_proxy
248
+ # don't overwrite prompts defined in the server
249
+ if prompt.name not in prompts:
250
+ prompt_proxy = await ProxyPrompt.from_client(self.client, prompt)
251
+ prompts[prompt_proxy.name] = prompt_proxy
252
+
242
253
  return prompts
243
254
 
244
- async def _mcp_call_tool(
245
- self, key: str, arguments: dict[str, Any]
246
- ) -> list[TextContent | ImageContent | EmbeddedResource]:
255
+ async def _call_tool(self, key: str, arguments: dict[str, Any]) -> list[MCPContent]:
247
256
  try:
248
- result = await super()._mcp_call_tool(key, arguments)
257
+ result = await super()._call_tool(key, arguments)
249
258
  return result
250
259
  except NotFoundError:
251
260
  async with self.client:
252
261
  result = await self.client.call_tool(key, arguments)
253
262
  return result
254
263
 
255
- async def _mcp_read_resource(self, uri: AnyUrl | str) -> list[ReadResourceContents]:
264
+ async def _read_resource(self, uri: AnyUrl | str) -> list[ReadResourceContents]:
256
265
  try:
257
- result = await super()._mcp_read_resource(uri)
266
+ result = await super()._read_resource(uri)
258
267
  return result
259
268
  except NotFoundError:
260
269
  async with self.client:
@@ -270,11 +279,11 @@ class FastMCPProxy(FastMCP):
270
279
  ReadResourceContents(content=content, mime_type=resource[0].mimeType)
271
280
  ]
272
281
 
273
- async def _mcp_get_prompt(
282
+ async def _get_prompt(
274
283
  self, name: str, arguments: dict[str, Any] | None = None
275
284
  ) -> GetPromptResult:
276
285
  try:
277
- result = await super()._mcp_get_prompt(name, arguments)
286
+ result = await super()._get_prompt(name, arguments)
278
287
  return result
279
288
  except NotFoundError:
280
289
  async with self.client: