fastmcp 2.7.0__py3-none-any.whl → 2.8.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.
fastmcp/__init__.py CHANGED
@@ -1,6 +1,9 @@
1
1
  """FastMCP - An ergonomic MCP interface."""
2
2
 
3
3
  from importlib.metadata import version
4
+ from fastmcp.settings import Settings
5
+
6
+ settings = Settings()
4
7
 
5
8
  from fastmcp.server.server import FastMCP
6
9
  from fastmcp.server.context import Context
@@ -8,7 +11,7 @@ import fastmcp.server
8
11
 
9
12
  from fastmcp.client import Client
10
13
  from fastmcp.utilities.types import Image
11
- from . import client, settings
14
+ from . import client
12
15
 
13
16
  __version__ = version("fastmcp")
14
17
  __all__ = [
fastmcp/cli/cli.py CHANGED
@@ -1,4 +1,4 @@
1
- """FastmMCP CLI tools."""
1
+ """FastMCP CLI tools."""
2
2
 
3
3
  import importlib.metadata
4
4
  import importlib.util
@@ -18,6 +18,7 @@ from typer import Context, Exit
18
18
  import fastmcp
19
19
  from fastmcp.cli import claude
20
20
  from fastmcp.cli import run as run_module
21
+ from fastmcp.server.server import FastMCP
21
22
  from fastmcp.utilities.logging import get_logger
22
23
 
23
24
  logger = get_logger("cli")
@@ -165,8 +166,8 @@ def dev(
165
166
 
166
167
  try:
167
168
  # Import server to get dependencies
168
- server = run_module.import_server(file, server_object)
169
- if hasattr(server, "dependencies") and server.dependencies is not None:
169
+ server: FastMCP = run_module.import_server(file, server_object)
170
+ if server.dependencies is not None:
170
171
  with_packages = list(set(with_packages + server.dependencies))
171
172
 
172
173
  env_vars = {}
@@ -23,10 +23,10 @@ from mcp.shared.auth import (
23
23
  )
24
24
  from pydantic import AnyHttpUrl, ValidationError
25
25
 
26
+ from fastmcp import settings as fastmcp_global_settings
26
27
  from fastmcp.client.oauth_callback import (
27
28
  create_oauth_callback_server,
28
29
  )
29
- from fastmcp.settings import settings as fastmcp_global_settings
30
30
  from fastmcp.utilities.http import find_available_port
31
31
  from fastmcp.utilities.logging import get_logger
32
32
 
fastmcp/client/client.py CHANGED
@@ -145,6 +145,7 @@ class Client(Generic[ClientTransportT]):
145
145
  progress_handler: ProgressHandler | None = None,
146
146
  timeout: datetime.timedelta | float | int | None = None,
147
147
  init_timeout: datetime.timedelta | float | int | None = None,
148
+ client_info: mcp.types.Implementation | None = None,
148
149
  auth: httpx.Auth | Literal["oauth"] | str | None = None,
149
150
  ):
150
151
  self.transport = cast(ClientTransportT, infer_transport(transport))
@@ -165,7 +166,7 @@ class Client(Generic[ClientTransportT]):
165
166
 
166
167
  # handle init handshake timeout
167
168
  if init_timeout is None:
168
- init_timeout = fastmcp.settings.settings.client_init_timeout
169
+ init_timeout = fastmcp.settings.client_init_timeout
169
170
  if isinstance(init_timeout, datetime.timedelta):
170
171
  init_timeout = init_timeout.total_seconds()
171
172
  elif not init_timeout:
@@ -180,6 +181,7 @@ class Client(Generic[ClientTransportT]):
180
181
  "logging_callback": create_log_callback(log_handler),
181
182
  "message_handler": message_handler,
182
183
  "read_timeout_seconds": timeout,
184
+ "client_info": client_info,
183
185
  }
184
186
 
185
187
  if roots is not None:
@@ -8,39 +8,25 @@ import sys
8
8
  import warnings
9
9
  from collections.abc import AsyncIterator, Callable
10
10
  from pathlib import Path
11
- from typing import (
12
- TYPE_CHECKING,
13
- Any,
14
- Literal,
15
- TypedDict,
16
- TypeVar,
17
- cast,
18
- overload,
19
- )
11
+ from typing import Any, Literal, TypedDict, TypeVar, cast, overload
20
12
 
21
13
  import anyio
22
14
  import httpx
15
+ import mcp.types
23
16
  from mcp import ClientSession, StdioServerParameters
24
- from mcp.client.session import (
25
- ListRootsFnT,
26
- LoggingFnT,
27
- MessageHandlerFnT,
28
- SamplingFnT,
29
- )
17
+ from mcp.client.session import ListRootsFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT
30
18
  from mcp.server.fastmcp import FastMCP as FastMCP1Server
31
- from mcp.shared.memory import create_connected_server_and_client_session
19
+ from mcp.shared.memory import create_client_server_memory_streams
32
20
  from pydantic import AnyUrl
33
21
  from typing_extensions import Unpack
34
22
 
23
+ from fastmcp.client.auth.bearer import BearerAuth
35
24
  from fastmcp.client.auth.oauth import OAuth
36
25
  from fastmcp.server.dependencies import get_http_headers
37
26
  from fastmcp.server.server import FastMCP
38
27
  from fastmcp.utilities.logging import get_logger
39
28
  from fastmcp.utilities.mcp_config import MCPConfig, infer_transport_type_from_url
40
29
 
41
- if TYPE_CHECKING:
42
- from fastmcp.utilities.mcp_config import MCPConfig
43
-
44
30
  logger = get_logger(__name__)
45
31
 
46
32
  # TypeVar for preserving specific ClientTransport subclass types
@@ -64,11 +50,12 @@ __all__ = [
64
50
  class SessionKwargs(TypedDict, total=False):
65
51
  """Keyword arguments for the MCP ClientSession constructor."""
66
52
 
53
+ read_timeout_seconds: datetime.timedelta | None
67
54
  sampling_callback: SamplingFnT | None
68
55
  list_roots_callback: ListRootsFnT | None
69
56
  logging_callback: LoggingFnT | None
70
57
  message_handler: MessageHandlerFnT | None
71
- read_timeout_seconds: datetime.timedelta | None
58
+ client_info: mcp.types.Implementation | None
72
59
 
73
60
 
74
61
  class ClientTransport(abc.ABC):
@@ -152,7 +139,7 @@ class WSTransport(ClientTransport):
152
139
  yield session
153
140
 
154
141
  def __repr__(self) -> str:
155
- return f"<WebSocket(url='{self.url}')>"
142
+ return f"<WebSocketTransport(url='{self.url}')>"
156
143
 
157
144
 
158
145
  class SSETransport(ClientTransport):
@@ -183,8 +170,7 @@ class SSETransport(ClientTransport):
183
170
  if auth == "oauth":
184
171
  auth = OAuth(self.url)
185
172
  elif isinstance(auth, str):
186
- self.headers["Authorization"] = auth
187
- auth = None
173
+ auth = BearerAuth(auth)
188
174
  self.auth = auth
189
175
 
190
176
  @contextlib.asynccontextmanager
@@ -221,7 +207,7 @@ class SSETransport(ClientTransport):
221
207
  yield session
222
208
 
223
209
  def __repr__(self) -> str:
224
- return f"<SSE(url='{self.url}')>"
210
+ return f"<SSETransport(url='{self.url}')>"
225
211
 
226
212
 
227
213
  class StreamableHttpTransport(ClientTransport):
@@ -252,8 +238,7 @@ class StreamableHttpTransport(ClientTransport):
252
238
  if auth == "oauth":
253
239
  auth = OAuth(self.url)
254
240
  elif isinstance(auth, str):
255
- self.headers["Authorization"] = auth
256
- auth = None
241
+ auth = BearerAuth(auth)
257
242
  self.auth = auth
258
243
 
259
244
  @contextlib.asynccontextmanager
@@ -291,7 +276,7 @@ class StreamableHttpTransport(ClientTransport):
291
276
  yield session
292
277
 
293
278
  def __repr__(self) -> str:
294
- return f"<StreamableHttp(url='{self.url}')>"
279
+ return f"<StreamableHttpTransport(url='{self.url}')>"
295
280
 
296
281
 
297
282
  class StdioTransport(ClientTransport):
@@ -663,27 +648,49 @@ class FastMCPTransport(ClientTransport):
663
648
  tests or scenarios where client and server run in the same runtime.
664
649
  """
665
650
 
666
- def __init__(self, mcp: FastMCP | FastMCP1Server):
651
+ def __init__(self, mcp: FastMCP | FastMCP1Server, raise_exceptions: bool = False):
667
652
  """Initialize a FastMCPTransport from a FastMCP server instance."""
668
653
 
669
654
  # Accept both FastMCP 2.x and FastMCP 1.0 servers. Both expose a
670
655
  # ``_mcp_server`` attribute pointing to the underlying MCP server
671
656
  # implementation, so we can treat them identically.
672
657
  self.server = mcp
658
+ self.raise_exceptions = raise_exceptions
673
659
 
674
660
  @contextlib.asynccontextmanager
675
661
  async def connect_session(
676
662
  self, **session_kwargs: Unpack[SessionKwargs]
677
663
  ) -> AsyncIterator[ClientSession]:
678
- # create_connected_server_and_client_session manages the session lifecycle itself
679
- async with create_connected_server_and_client_session(
680
- server=self.server._mcp_server,
681
- **session_kwargs,
682
- ) as session:
683
- yield session
664
+ async with create_client_server_memory_streams() as (
665
+ client_streams,
666
+ server_streams,
667
+ ):
668
+ client_read, client_write = client_streams
669
+ server_read, server_write = server_streams
670
+
671
+ # Create a cancel scope for the server task
672
+ async with anyio.create_task_group() as tg:
673
+ tg.start_soon(
674
+ lambda: self.server._mcp_server.run(
675
+ server_read,
676
+ server_write,
677
+ self.server._mcp_server.create_initialization_options(),
678
+ raise_exceptions=self.raise_exceptions,
679
+ )
680
+ )
681
+
682
+ try:
683
+ async with ClientSession(
684
+ read_stream=client_read,
685
+ write_stream=client_write,
686
+ **session_kwargs,
687
+ ) as client_session:
688
+ yield client_session
689
+ finally:
690
+ tg.cancel_scope.cancel()
684
691
 
685
692
  def __repr__(self) -> str:
686
- return f"<FastMCP(server='{self.server.name}')>"
693
+ return f"<FastMCPTransport(server='{self.server.name}')>"
687
694
 
688
695
 
689
696
  class MCPConfigTransport(ClientTransport):
@@ -769,7 +776,7 @@ class MCPConfigTransport(ClientTransport):
769
776
  yield session
770
777
 
771
778
  def __repr__(self) -> str:
772
- return f"<MCPConfig(config='{self.config}')>"
779
+ return f"<MCPConfigTransport(config='{self.config}')>"
773
780
 
774
781
 
775
782
  @overload
@@ -860,7 +867,6 @@ def infer_transport(
860
867
  transport = infer_transport(config)
861
868
  ```
862
869
  """
863
- from fastmcp.utilities.mcp_config import MCPConfig
864
870
 
865
871
  # the transport is already a ClientTransport
866
872
  if isinstance(transport, ClientTransport):
fastmcp/exceptions.py CHANGED
@@ -33,3 +33,7 @@ class ClientError(Exception):
33
33
 
34
34
  class NotFoundError(Exception):
35
35
  """Object not found."""
36
+
37
+
38
+ class DisabledError(Exception):
39
+ """Object is disabled."""
fastmcp/prompts/prompt.py CHANGED
@@ -5,21 +5,21 @@ from __future__ import annotations as _annotations
5
5
  import inspect
6
6
  from abc import ABC, abstractmethod
7
7
  from collections.abc import Awaitable, Callable, Sequence
8
- from typing import TYPE_CHECKING, Annotated, Any
8
+ from typing import TYPE_CHECKING, Any
9
9
 
10
10
  import pydantic_core
11
11
  from mcp.types import EmbeddedResource, ImageContent, PromptMessage, Role, TextContent
12
12
  from mcp.types import Prompt as MCPPrompt
13
13
  from mcp.types import PromptArgument as MCPPromptArgument
14
- from pydantic import BeforeValidator, Field, TypeAdapter, validate_call
14
+ from pydantic import Field, TypeAdapter, validate_call
15
15
 
16
16
  from fastmcp.exceptions import PromptError
17
17
  from fastmcp.server.dependencies import get_context
18
+ from fastmcp.utilities.components import FastMCPComponent
18
19
  from fastmcp.utilities.json_schema import compress_schema
19
20
  from fastmcp.utilities.logging import get_logger
20
21
  from fastmcp.utilities.types import (
21
22
  FastMCPBaseModel,
22
- _convert_set_defaults,
23
23
  find_kwarg_by_type,
24
24
  get_cached_typeadapter,
25
25
  )
@@ -59,33 +59,20 @@ class PromptArgument(FastMCPBaseModel):
59
59
 
60
60
  name: str = Field(description="Name of the argument")
61
61
  description: str | None = Field(
62
- None, description="Description of what the argument does"
62
+ default=None, description="Description of what the argument does"
63
63
  )
64
64
  required: bool = Field(
65
65
  default=False, description="Whether the argument is required"
66
66
  )
67
67
 
68
68
 
69
- class Prompt(FastMCPBaseModel, ABC):
69
+ class Prompt(FastMCPComponent, ABC):
70
70
  """A prompt template that can be rendered with parameters."""
71
71
 
72
- name: str = Field(description="Name of the prompt")
73
- description: str | None = Field(
74
- None, description="Description of what the prompt does"
75
- )
76
- tags: Annotated[set[str], BeforeValidator(_convert_set_defaults)] = Field(
77
- default_factory=set, description="Tags for the prompt"
78
- )
79
72
  arguments: list[PromptArgument] | None = Field(
80
- None, description="Arguments that can be passed to the prompt"
73
+ default=None, description="Arguments that can be passed to the prompt"
81
74
  )
82
75
 
83
- def __eq__(self, other: object) -> bool:
84
- if type(self) is not type(other):
85
- return False
86
- assert isinstance(other, type(self))
87
- return self.model_dump() == other.model_dump()
88
-
89
76
  def to_mcp_prompt(self, **overrides: Any) -> MCPPrompt:
90
77
  """Convert the prompt to an MCP prompt."""
91
78
  arguments = [
@@ -109,6 +96,7 @@ class Prompt(FastMCPBaseModel, ABC):
109
96
  name: str | None = None,
110
97
  description: str | None = None,
111
98
  tags: set[str] | None = None,
99
+ enabled: bool | None = None,
112
100
  ) -> FunctionPrompt:
113
101
  """Create a Prompt from a function.
114
102
 
@@ -119,7 +107,7 @@ class Prompt(FastMCPBaseModel, ABC):
119
107
  - A sequence of any of the above
120
108
  """
121
109
  return FunctionPrompt.from_function(
122
- fn=fn, name=name, description=description, tags=tags
110
+ fn=fn, name=name, description=description, tags=tags, enabled=enabled
123
111
  )
124
112
 
125
113
  @abstractmethod
@@ -143,6 +131,7 @@ class FunctionPrompt(Prompt):
143
131
  name: str | None = None,
144
132
  description: str | None = None,
145
133
  tags: set[str] | None = None,
134
+ enabled: bool | None = None,
146
135
  ) -> FunctionPrompt:
147
136
  """Create a Prompt from a function.
148
137
 
@@ -208,6 +197,7 @@ class FunctionPrompt(Prompt):
208
197
  description=description,
209
198
  arguments=arguments,
210
199
  tags=tags or set(),
200
+ enabled=enabled if enabled is not None else True,
211
201
  fn=fn,
212
202
  )
213
203
 
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Any
6
6
 
7
7
  from mcp import GetPromptResult
8
8
 
9
+ from fastmcp import settings
9
10
  from fastmcp.exceptions import NotFoundError, PromptError
10
11
  from fastmcp.prompts.prompt import FunctionPrompt, Prompt, PromptResult
11
12
  from fastmcp.settings import DuplicateBehavior
@@ -23,10 +24,10 @@ class PromptManager:
23
24
  def __init__(
24
25
  self,
25
26
  duplicate_behavior: DuplicateBehavior | None = None,
26
- mask_error_details: bool = False,
27
+ mask_error_details: bool | None = None,
27
28
  ):
28
29
  self._prompts: dict[str, Prompt] = {}
29
- self.mask_error_details = mask_error_details
30
+ self.mask_error_details = mask_error_details or settings.mask_error_details
30
31
 
31
32
  # Default to "warn" if None is provided
32
33
  if duplicate_behavior is None:
@@ -40,9 +41,11 @@ class PromptManager:
40
41
 
41
42
  self.duplicate_behavior = duplicate_behavior
42
43
 
43
- def get_prompt(self, key: str) -> Prompt | None:
44
+ def get_prompt(self, key: str) -> Prompt:
44
45
  """Get prompt by key."""
45
- return self._prompts.get(key)
46
+ if key in self._prompts:
47
+ return self._prompts[key]
48
+ raise NotFoundError(f"Unknown prompt: {key}")
46
49
 
47
50
  def get_prompts(self) -> dict[str, Prompt]:
48
51
  """Get all registered prompts, indexed by registered key."""
@@ -11,18 +11,17 @@ import pydantic_core
11
11
  from mcp.types import Resource as MCPResource
12
12
  from pydantic import (
13
13
  AnyUrl,
14
- BeforeValidator,
15
14
  ConfigDict,
16
15
  Field,
17
16
  UrlConstraints,
18
- ValidationInfo,
19
17
  field_validator,
18
+ model_validator,
20
19
  )
20
+ from typing_extensions import Self
21
21
 
22
22
  from fastmcp.server.dependencies import get_context
23
+ from fastmcp.utilities.components import FastMCPComponent
23
24
  from fastmcp.utilities.types import (
24
- FastMCPBaseModel,
25
- _convert_set_defaults,
26
25
  find_kwarg_by_type,
27
26
  )
28
27
 
@@ -30,7 +29,7 @@ if TYPE_CHECKING:
30
29
  pass
31
30
 
32
31
 
33
- class Resource(FastMCPBaseModel, abc.ABC):
32
+ class Resource(FastMCPComponent, abc.ABC):
34
33
  """Base class for all resources."""
35
34
 
36
35
  model_config = ConfigDict(validate_default=True)
@@ -38,13 +37,7 @@ class Resource(FastMCPBaseModel, abc.ABC):
38
37
  uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] = Field(
39
38
  default=..., description="URI of the resource"
40
39
  )
41
- name: str | None = Field(description="Name of the resource", default=None)
42
- description: str | None = Field(
43
- description="Description of the resource", default=None
44
- )
45
- tags: Annotated[set[str], BeforeValidator(_convert_set_defaults)] = Field(
46
- default_factory=set, description="Tags for the resource"
47
- )
40
+ name: str = Field(default="", description="Name of the resource")
48
41
  mime_type: str = Field(
49
42
  default="text/plain",
50
43
  description="MIME type of the resource content",
@@ -59,6 +52,7 @@ class Resource(FastMCPBaseModel, abc.ABC):
59
52
  description: str | None = None,
60
53
  mime_type: str | None = None,
61
54
  tags: set[str] | None = None,
55
+ enabled: bool | None = None,
62
56
  ) -> FunctionResource:
63
57
  return FunctionResource.from_function(
64
58
  fn=fn,
@@ -67,6 +61,7 @@ class Resource(FastMCPBaseModel, abc.ABC):
67
61
  description=description,
68
62
  mime_type=mime_type,
69
63
  tags=tags,
64
+ enabled=enabled,
70
65
  )
71
66
 
72
67
  @field_validator("mime_type", mode="before")
@@ -77,27 +72,22 @@ class Resource(FastMCPBaseModel, abc.ABC):
77
72
  return mime_type
78
73
  return "text/plain"
79
74
 
80
- @field_validator("name", mode="before")
81
- @classmethod
82
- def set_default_name(cls, name: str | None, info: ValidationInfo) -> str:
75
+ @model_validator(mode="after")
76
+ def set_default_name(self) -> Self:
83
77
  """Set default name from URI if not provided."""
84
- if name:
85
- return name
86
- if uri := info.data.get("uri"):
87
- return str(uri)
88
- raise ValueError("Either name or uri must be provided")
78
+ if self.name:
79
+ pass
80
+ elif self.uri:
81
+ self.name = str(self.uri)
82
+ else:
83
+ raise ValueError("Either name or uri must be provided")
84
+ return self
89
85
 
90
86
  @abc.abstractmethod
91
87
  async def read(self) -> str | bytes:
92
88
  """Read the resource content."""
93
89
  pass
94
90
 
95
- def __eq__(self, other: object) -> bool:
96
- if type(self) is not type(other):
97
- return False
98
- assert isinstance(other, type(self))
99
- return self.model_dump() == other.model_dump()
100
-
101
91
  def to_mcp_resource(self, **overrides: Any) -> MCPResource:
102
92
  """Convert the resource to an MCPResource."""
103
93
  kwargs = {
@@ -108,6 +98,9 @@ class Resource(FastMCPBaseModel, abc.ABC):
108
98
  }
109
99
  return MCPResource(**kwargs | overrides)
110
100
 
101
+ def __repr__(self) -> str:
102
+ return f"{self.__class__.__name__}(uri={self.uri!r}, name={self.name!r}, description={self.description!r}, tags={self.tags})"
103
+
111
104
 
112
105
  class FunctionResource(Resource):
113
106
  """A resource that defers data loading by wrapping a function.
@@ -133,6 +126,7 @@ class FunctionResource(Resource):
133
126
  description: str | None = None,
134
127
  mime_type: str | None = None,
135
128
  tags: set[str] | None = None,
129
+ enabled: bool | None = None,
136
130
  ) -> FunctionResource:
137
131
  """Create a FunctionResource from a function."""
138
132
  if isinstance(uri, str):
@@ -144,6 +138,7 @@ class FunctionResource(Resource):
144
138
  description=description or fn.__doc__,
145
139
  mime_type=mime_type or "text/plain",
146
140
  tags=tags or set(),
141
+ enabled=enabled if enabled is not None else True,
147
142
  )
148
143
 
149
144
  async def read(self) -> str | bytes:
@@ -7,6 +7,7 @@ from typing import Any
7
7
 
8
8
  from pydantic import AnyUrl
9
9
 
10
+ from fastmcp import settings
10
11
  from fastmcp.exceptions import NotFoundError, ResourceError
11
12
  from fastmcp.resources.resource import Resource
12
13
  from fastmcp.resources.template import (
@@ -25,7 +26,7 @@ class ResourceManager:
25
26
  def __init__(
26
27
  self,
27
28
  duplicate_behavior: DuplicateBehavior | None = None,
28
- mask_error_details: bool = False,
29
+ mask_error_details: bool | None = None,
29
30
  ):
30
31
  """Initialize the ResourceManager.
31
32
 
@@ -37,7 +38,7 @@ class ResourceManager:
37
38
  """
38
39
  self._resources: dict[str, Resource] = {}
39
40
  self._templates: dict[str, ResourceTemplate] = {}
40
- self.mask_error_details = mask_error_details
41
+ self.mask_error_details = mask_error_details or settings.mask_error_details
41
42
 
42
43
  # Default to "warn" if None is provided
43
44
  if duplicate_behavior is None:
@@ -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())