fastmcp 2.10.5__py3-none-any.whl → 2.11.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 (65) hide show
  1. fastmcp/__init__.py +7 -2
  2. fastmcp/cli/cli.py +128 -33
  3. fastmcp/cli/install/__init__.py +2 -2
  4. fastmcp/cli/install/claude_code.py +42 -1
  5. fastmcp/cli/install/claude_desktop.py +42 -1
  6. fastmcp/cli/install/cursor.py +42 -1
  7. fastmcp/cli/install/{mcp_config.py → mcp_json.py} +51 -7
  8. fastmcp/cli/run.py +127 -1
  9. fastmcp/client/__init__.py +2 -0
  10. fastmcp/client/auth/oauth.py +68 -99
  11. fastmcp/client/oauth_callback.py +18 -0
  12. fastmcp/client/transports.py +69 -15
  13. fastmcp/contrib/component_manager/example.py +2 -2
  14. fastmcp/experimental/server/openapi/README.md +266 -0
  15. fastmcp/experimental/server/openapi/__init__.py +38 -0
  16. fastmcp/experimental/server/openapi/components.py +348 -0
  17. fastmcp/experimental/server/openapi/routing.py +132 -0
  18. fastmcp/experimental/server/openapi/server.py +466 -0
  19. fastmcp/experimental/utilities/openapi/README.md +239 -0
  20. fastmcp/experimental/utilities/openapi/__init__.py +68 -0
  21. fastmcp/experimental/utilities/openapi/director.py +208 -0
  22. fastmcp/experimental/utilities/openapi/formatters.py +355 -0
  23. fastmcp/experimental/utilities/openapi/json_schema_converter.py +340 -0
  24. fastmcp/experimental/utilities/openapi/models.py +85 -0
  25. fastmcp/experimental/utilities/openapi/parser.py +618 -0
  26. fastmcp/experimental/utilities/openapi/schemas.py +538 -0
  27. fastmcp/mcp_config.py +125 -88
  28. fastmcp/prompts/prompt.py +11 -1
  29. fastmcp/prompts/prompt_manager.py +1 -1
  30. fastmcp/resources/resource.py +21 -1
  31. fastmcp/resources/resource_manager.py +2 -2
  32. fastmcp/resources/template.py +20 -1
  33. fastmcp/server/auth/__init__.py +17 -2
  34. fastmcp/server/auth/auth.py +144 -7
  35. fastmcp/server/auth/providers/bearer.py +25 -473
  36. fastmcp/server/auth/providers/in_memory.py +4 -2
  37. fastmcp/server/auth/providers/jwt.py +538 -0
  38. fastmcp/server/auth/providers/workos.py +170 -0
  39. fastmcp/server/auth/registry.py +52 -0
  40. fastmcp/server/context.py +110 -26
  41. fastmcp/server/dependencies.py +9 -2
  42. fastmcp/server/http.py +62 -30
  43. fastmcp/server/middleware/middleware.py +3 -23
  44. fastmcp/server/openapi.py +26 -13
  45. fastmcp/server/proxy.py +89 -8
  46. fastmcp/server/server.py +170 -62
  47. fastmcp/settings.py +83 -18
  48. fastmcp/tools/tool.py +41 -6
  49. fastmcp/tools/tool_manager.py +39 -3
  50. fastmcp/tools/tool_transform.py +122 -6
  51. fastmcp/utilities/components.py +35 -2
  52. fastmcp/utilities/json_schema.py +136 -98
  53. fastmcp/utilities/json_schema_type.py +1 -3
  54. fastmcp/utilities/mcp_config.py +28 -0
  55. fastmcp/utilities/openapi.py +306 -30
  56. fastmcp/utilities/tests.py +54 -6
  57. fastmcp/utilities/types.py +89 -11
  58. {fastmcp-2.10.5.dist-info → fastmcp-2.11.0.dist-info}/METADATA +4 -3
  59. fastmcp-2.11.0.dist-info/RECORD +108 -0
  60. fastmcp/server/auth/providers/bearer_env.py +0 -63
  61. fastmcp/utilities/cache.py +0 -26
  62. fastmcp-2.10.5.dist-info/RECORD +0 -93
  63. {fastmcp-2.10.5.dist-info → fastmcp-2.11.0.dist-info}/WHEEL +0 -0
  64. {fastmcp-2.10.5.dist-info → fastmcp-2.11.0.dist-info}/entry_points.txt +0 -0
  65. {fastmcp-2.10.5.dist-info → fastmcp-2.11.0.dist-info}/licenses/LICENSE +0 -0
fastmcp/mcp_config.py CHANGED
@@ -7,33 +7,47 @@ The configuration format supports both stdio and remote (HTTP/SSE) transports, w
7
7
  field definitions for server metadata, authentication, and execution parameters.
8
8
 
9
9
  Example configuration:
10
- {
11
- "mcpServers": {
12
- "my-server": {
13
- "command": "npx",
14
- "args": ["-y", "@my/mcp-server"],
15
- "env": {"API_KEY": "secret"},
16
- "timeout": 30000,
17
- "description": "My MCP server"
18
- }
10
+ ```json
11
+ {
12
+ "mcpServers": {
13
+ "my-server": {
14
+ "command": "npx",
15
+ "args": ["-y", "@my/mcp-server"],
16
+ "env": {"API_KEY": "secret"},
17
+ "timeout": 30000,
18
+ "description": "My MCP server"
19
19
  }
20
20
  }
21
+ }
22
+ ```
21
23
  """
22
24
 
23
25
  from __future__ import annotations
24
26
 
25
27
  import datetime
26
- import json
27
28
  import re
28
29
  from pathlib import Path
29
30
  from typing import TYPE_CHECKING, Annotated, Any, Literal
30
31
  from urllib.parse import urlparse
31
32
 
32
33
  import httpx
33
- from pydantic import AnyUrl, BaseModel, ConfigDict, Field
34
+ from pydantic import (
35
+ AnyUrl,
36
+ BaseModel,
37
+ ConfigDict,
38
+ Field,
39
+ ValidationInfo,
40
+ model_validator,
41
+ )
42
+ from typing_extensions import Self, override
43
+
44
+ from fastmcp.tools.tool_transform import ToolTransformConfig
45
+ from fastmcp.utilities.types import FastMCPBaseModel
34
46
 
35
47
  if TYPE_CHECKING:
36
48
  from fastmcp.client.transports import (
49
+ ClientTransport,
50
+ FastMCPTransport,
37
51
  SSETransport,
38
52
  StdioTransport,
39
53
  StreamableHttpTransport,
@@ -60,6 +74,39 @@ def infer_transport_type_from_url(
60
74
  return "http"
61
75
 
62
76
 
77
+ class _TransformingMCPServerMixin(FastMCPBaseModel):
78
+ """A mixin that enables wrapping an MCP Server with tool transforms."""
79
+
80
+ tools: dict[str, ToolTransformConfig] = Field(...)
81
+ """The multi-tool transform to apply to the tools."""
82
+
83
+ include_tags: set[str] | None = Field(
84
+ default=None,
85
+ description="The tags to include in the proxy.",
86
+ )
87
+
88
+ exclude_tags: set[str] | None = Field(
89
+ default=None,
90
+ description="The tags to exclude in the proxy.",
91
+ )
92
+
93
+ def to_transport(self) -> FastMCPTransport:
94
+ """Get the transport for the server."""
95
+ from fastmcp.client.transports import FastMCPTransport
96
+ from fastmcp.server.server import FastMCP
97
+
98
+ transport: ClientTransport = super().to_transport() # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue, reportUnknownVariableType]
99
+
100
+ wrapped_mcp_server = FastMCP.as_proxy(
101
+ transport,
102
+ tool_transformations=self.tools,
103
+ include_tags=self.include_tags,
104
+ exclude_tags=self.exclude_tags,
105
+ )
106
+
107
+ return FastMCPTransport(wrapped_mcp_server)
108
+
109
+
63
110
  class StdioMCPServer(BaseModel):
64
111
  """MCP server configuration for stdio transport.
65
112
 
@@ -101,6 +148,10 @@ class StdioMCPServer(BaseModel):
101
148
  )
102
149
 
103
150
 
151
+ class TransformingStdioMCPServer(_TransformingMCPServerMixin, StdioMCPServer):
152
+ """A Stdio server with tool transforms."""
153
+
154
+
104
155
  class RemoteMCPServer(BaseModel):
105
156
  """MCP server configuration for HTTP/SSE transport.
106
157
 
@@ -162,120 +213,106 @@ class RemoteMCPServer(BaseModel):
162
213
  )
163
214
 
164
215
 
216
+ class TransformingRemoteMCPServer(_TransformingMCPServerMixin, RemoteMCPServer):
217
+ """A Remote server with tool transforms."""
218
+
219
+
220
+ TransformingMCPServerTypes = TransformingStdioMCPServer | TransformingRemoteMCPServer
221
+
222
+ CanonicalMCPServerTypes = StdioMCPServer | RemoteMCPServer
223
+
224
+ MCPServerTypes = TransformingMCPServerTypes | CanonicalMCPServerTypes
225
+
226
+
165
227
  class MCPConfig(BaseModel):
166
- """Canonical MCP configuration format.
228
+ """A configuration object for MCP Servers that conforms to the canonical MCP configuration format
229
+ while adding additional fields for enabling FastMCP-specific features like tool transformations
230
+ and filtering by tags.
167
231
 
168
- This defines the standard configuration format for Model Context Protocol servers.
169
- The format is designed to be client-agnostic and extensible for future use cases.
232
+ For an MCPConfig that is strictly canonical, see the `CanonicalMCPConfig` class.
170
233
  """
171
234
 
172
- mcpServers: dict[str, StdioMCPServer | RemoteMCPServer]
235
+ mcpServers: dict[str, MCPServerTypes]
173
236
 
174
237
  model_config = ConfigDict(extra="allow") # Preserve unknown top-level fields
175
238
 
239
+ @model_validator(mode="before")
240
+ def validate_mcp_servers(self, info: ValidationInfo) -> dict[str, Any]:
241
+ """Validate the MCP servers."""
242
+ if not isinstance(self, dict):
243
+ raise ValueError("MCPConfig format requires a dictionary of servers.")
244
+
245
+ if "mcpServers" not in self:
246
+ self = {"mcpServers": self}
247
+
248
+ return self
249
+
250
+ def add_server(self, name: str, server: MCPServerTypes) -> None:
251
+ """Add or update a server in the configuration."""
252
+ self.mcpServers[name] = server
253
+
176
254
  @classmethod
177
- def from_dict(cls, config: dict[str, Any]) -> MCPConfig:
255
+ def from_dict(cls, config: dict[str, Any]) -> Self:
178
256
  """Parse MCP configuration from dictionary format."""
179
- # Handle case where config is just the mcpServers object
180
- if "mcpServers" not in config and any(
181
- isinstance(v, dict) and ("command" in v or "url" in v)
182
- for v in config.values()
183
- ):
184
- # This looks like a bare mcpServers object
185
- servers_dict = config
186
- else:
187
- # Standard format with mcpServers wrapper
188
- servers_dict = config.get("mcpServers", {})
189
-
190
- # Parse each server configuration
191
- parsed_servers = {}
192
- for name, server_config in servers_dict.items():
193
- if not isinstance(server_config, dict):
194
- continue
195
-
196
- # Determine if this is stdio or remote based on fields
197
- if "command" in server_config:
198
- parsed_servers[name] = StdioMCPServer.model_validate(server_config)
199
- elif "url" in server_config:
200
- parsed_servers[name] = RemoteMCPServer.model_validate(server_config)
201
- else:
202
- # Skip invalid server configs but preserve them as raw dicts
203
- # This allows for forward compatibility with unknown server types
204
- continue
205
-
206
- # Create config with any extra top-level fields preserved
207
- config_data = {k: v for k, v in config.items() if k != "mcpServers"}
208
- config_data["mcpServers"] = parsed_servers
209
-
210
- return cls.model_validate(config_data)
257
+ return cls.model_validate(config)
211
258
 
212
259
  def to_dict(self) -> dict[str, Any]:
213
260
  """Convert MCPConfig to dictionary format, preserving all fields."""
214
- # Start with all extra fields at the top level
215
- result = self.model_dump(exclude={"mcpServers"}, exclude_none=True)
216
-
217
- # Add mcpServers with all fields preserved
218
- result["mcpServers"] = {
219
- name: server.model_dump(exclude_none=True)
220
- for name, server in self.mcpServers.items()
221
- }
222
-
223
- return result
261
+ return self.model_dump(exclude_none=True)
224
262
 
225
263
  def write_to_file(self, file_path: Path) -> None:
226
264
  """Write configuration to JSON file."""
227
265
  file_path.parent.mkdir(parents=True, exist_ok=True)
228
- with open(file_path, "w") as f:
229
- json.dump(self.to_dict(), f, indent=2)
266
+ file_path.write_text(self.model_dump_json(indent=2))
230
267
 
231
268
  @classmethod
232
- def from_file(cls, file_path: Path) -> MCPConfig:
269
+ def from_file(cls, file_path: Path) -> Self:
233
270
  """Load configuration from JSON file."""
234
- if not file_path.exists():
235
- return cls(mcpServers={})
236
- with open(file_path) as f:
237
- content = f.read().strip()
238
- if not content:
239
- return cls(mcpServers={})
240
- data = json.loads(content)
241
- return cls.from_dict(data)
242
-
243
- def add_server(self, name: str, server: StdioMCPServer | RemoteMCPServer) -> None:
271
+ if file_path.exists():
272
+ if content := file_path.read_text().strip():
273
+ return cls.model_validate_json(content)
274
+
275
+ raise ValueError(f"No MCP servers defined in the config: {file_path}")
276
+
277
+
278
+ class CanonicalMCPConfig(MCPConfig):
279
+ """Canonical MCP configuration format.
280
+
281
+ This defines the standard configuration format for Model Context Protocol servers.
282
+ The format is designed to be client-agnostic and extensible for future use cases.
283
+ """
284
+
285
+ mcpServers: dict[str, CanonicalMCPServerTypes]
286
+
287
+ @override
288
+ def add_server(self, name: str, server: CanonicalMCPServerTypes) -> None:
244
289
  """Add or update a server in the configuration."""
245
290
  self.mcpServers[name] = server
246
291
 
247
- def remove_server(self, name: str) -> None:
248
- """Remove a server from the configuration."""
249
- if name in self.mcpServers:
250
- del self.mcpServers[name]
251
-
252
292
 
253
293
  def update_config_file(
254
294
  file_path: Path,
255
295
  server_name: str,
256
- server_config: StdioMCPServer | RemoteMCPServer,
296
+ server_config: CanonicalMCPServerTypes,
257
297
  ) -> None:
258
- """Update MCP configuration file with new server, preserving existing fields."""
298
+ """Update an MCP configuration file from a server object, preserving existing fields.
299
+
300
+ This is used for updating the mcpServer configurations of third-party tools so we do not
301
+ worry about transforming server objects here."""
259
302
  config = MCPConfig.from_file(file_path)
260
303
 
261
304
  # If updating an existing server, merge with existing configuration
262
305
  # to preserve any unknown fields
263
- if server_name in config.mcpServers:
264
- existing_server = config.mcpServers[server_name]
306
+ if existing_server := config.mcpServers.get(server_name):
265
307
  # Get the raw dict representation of both servers
266
308
  existing_dict = existing_server.model_dump()
309
+
267
310
  new_dict = server_config.model_dump(exclude_none=True)
268
311
 
269
312
  # Merge, with new values taking precedence
270
- merged_dict = {**existing_dict, **new_dict}
271
-
272
- # Create new server instance with merged data
273
- if "command" in merged_dict:
274
- merged_server = StdioMCPServer.model_validate(merged_dict)
275
- else:
276
- merged_server = RemoteMCPServer.model_validate(merged_dict)
313
+ merged_config = server_config.model_validate({**existing_dict, **new_dict})
277
314
 
278
- config.add_server(server_name, merged_server)
315
+ config.add_server(server_name, merged_config)
279
316
  else:
280
317
  config.add_server(server_name, server_config)
281
318
 
fastmcp/prompts/prompt.py CHANGED
@@ -85,7 +85,12 @@ class Prompt(FastMCPComponent, ABC):
85
85
  except RuntimeError:
86
86
  pass # No context available
87
87
 
88
- def to_mcp_prompt(self, **overrides: Any) -> MCPPrompt:
88
+ def to_mcp_prompt(
89
+ self,
90
+ *,
91
+ include_fastmcp_meta: bool | None = None,
92
+ **overrides: Any,
93
+ ) -> MCPPrompt:
89
94
  """Convert the prompt to an MCP prompt."""
90
95
  arguments = [
91
96
  MCPPromptArgument(
@@ -100,6 +105,7 @@ class Prompt(FastMCPComponent, ABC):
100
105
  "description": self.description,
101
106
  "arguments": arguments,
102
107
  "title": self.title,
108
+ "_meta": self.get_meta(include_fastmcp_meta=include_fastmcp_meta),
103
109
  }
104
110
  return MCPPrompt(**kwargs | overrides)
105
111
 
@@ -111,6 +117,7 @@ class Prompt(FastMCPComponent, ABC):
111
117
  description: str | None = None,
112
118
  tags: set[str] | None = None,
113
119
  enabled: bool | None = None,
120
+ meta: dict[str, Any] | None = None,
114
121
  ) -> FunctionPrompt:
115
122
  """Create a Prompt from a function.
116
123
 
@@ -127,6 +134,7 @@ class Prompt(FastMCPComponent, ABC):
127
134
  description=description,
128
135
  tags=tags,
129
136
  enabled=enabled,
137
+ meta=meta,
130
138
  )
131
139
 
132
140
  @abstractmethod
@@ -152,6 +160,7 @@ class FunctionPrompt(Prompt):
152
160
  description: str | None = None,
153
161
  tags: set[str] | None = None,
154
162
  enabled: bool | None = None,
163
+ meta: dict[str, Any] | None = None,
155
164
  ) -> FunctionPrompt:
156
165
  """Create a Prompt from a function.
157
166
 
@@ -246,6 +255,7 @@ class FunctionPrompt(Prompt):
246
255
  tags=tags or set(),
247
256
  enabled=enabled if enabled is not None else True,
248
257
  fn=fn,
258
+ meta=meta,
249
259
  )
250
260
 
251
261
  def _convert_string_arguments(self, kwargs: dict[str, Any]) -> dict[str, Any]:
@@ -78,7 +78,7 @@ class PromptManager:
78
78
  except Exception as e:
79
79
  # Skip failed mounts silently, matches existing behavior
80
80
  logger.warning(
81
- f"Failed to get prompts from mounted server '{mounted.prefix}': {e}"
81
+ f"Failed to get prompts from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}"
82
82
  )
83
83
  continue
84
84
 
@@ -8,6 +8,7 @@ from collections.abc import Callable
8
8
  from typing import TYPE_CHECKING, Annotated, Any
9
9
 
10
10
  import pydantic_core
11
+ from mcp.types import Annotations
11
12
  from mcp.types import Resource as MCPResource
12
13
  from pydantic import (
13
14
  AnyUrl,
@@ -43,6 +44,10 @@ class Resource(FastMCPComponent, abc.ABC):
43
44
  description="MIME type of the resource content",
44
45
  pattern=r"^[a-zA-Z0-9]+/[a-zA-Z0-9\-+.]+$",
45
46
  )
47
+ annotations: Annotated[
48
+ Annotations | None,
49
+ Field(description="Optional annotations about the resource's behavior"),
50
+ ] = None
46
51
 
47
52
  def enable(self) -> None:
48
53
  super().enable()
@@ -70,6 +75,8 @@ class Resource(FastMCPComponent, abc.ABC):
70
75
  mime_type: str | None = None,
71
76
  tags: set[str] | None = None,
72
77
  enabled: bool | None = None,
78
+ annotations: Annotations | None = None,
79
+ meta: dict[str, Any] | None = None,
73
80
  ) -> FunctionResource:
74
81
  return FunctionResource.from_function(
75
82
  fn=fn,
@@ -80,6 +87,8 @@ class Resource(FastMCPComponent, abc.ABC):
80
87
  mime_type=mime_type,
81
88
  tags=tags,
82
89
  enabled=enabled,
90
+ annotations=annotations,
91
+ meta=meta,
83
92
  )
84
93
 
85
94
  @field_validator("mime_type", mode="before")
@@ -106,7 +115,12 @@ class Resource(FastMCPComponent, abc.ABC):
106
115
  """Read the resource content."""
107
116
  pass
108
117
 
109
- def to_mcp_resource(self, **overrides: Any) -> MCPResource:
118
+ def to_mcp_resource(
119
+ self,
120
+ *,
121
+ include_fastmcp_meta: bool | None = None,
122
+ **overrides: Any,
123
+ ) -> MCPResource:
110
124
  """Convert the resource to an MCPResource."""
111
125
  kwargs = {
112
126
  "uri": self.uri,
@@ -114,6 +128,8 @@ class Resource(FastMCPComponent, abc.ABC):
114
128
  "description": self.description,
115
129
  "mimeType": self.mime_type,
116
130
  "title": self.title,
131
+ "annotations": self.annotations,
132
+ "_meta": self.get_meta(include_fastmcp_meta=include_fastmcp_meta),
117
133
  }
118
134
  return MCPResource(**kwargs | overrides)
119
135
 
@@ -157,6 +173,8 @@ class FunctionResource(Resource):
157
173
  mime_type: str | None = None,
158
174
  tags: set[str] | None = None,
159
175
  enabled: bool | None = None,
176
+ annotations: Annotations | None = None,
177
+ meta: dict[str, Any] | None = None,
160
178
  ) -> FunctionResource:
161
179
  """Create a FunctionResource from a function."""
162
180
  if isinstance(uri, str):
@@ -170,6 +188,8 @@ class FunctionResource(Resource):
170
188
  mime_type=mime_type or "text/plain",
171
189
  tags=tags or set(),
172
190
  enabled=enabled if enabled is not None else True,
191
+ annotations=annotations,
192
+ meta=meta,
173
193
  )
174
194
 
175
195
  async def read(self) -> str | bytes:
@@ -109,7 +109,7 @@ class ResourceManager:
109
109
  except Exception as e:
110
110
  # Skip failed mounts silently, matches existing behavior
111
111
  logger.warning(
112
- f"Failed to get resources from mounted server '{mounted.prefix}': {e}"
112
+ f"Failed to get resources from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}"
113
113
  )
114
114
  continue
115
115
 
@@ -157,7 +157,7 @@ class ResourceManager:
157
157
  except Exception as e:
158
158
  # Skip failed mounts silently, matches existing behavior
159
159
  logger.warning(
160
- f"Failed to get templates from mounted server '{mounted.prefix}': {e}"
160
+ f"Failed to get templates from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}"
161
161
  )
162
162
  continue
163
163
 
@@ -8,6 +8,7 @@ from collections.abc import Callable
8
8
  from typing import Any
9
9
  from urllib.parse import unquote
10
10
 
11
+ from mcp.types import Annotations
11
12
  from mcp.types import ResourceTemplate as MCPResourceTemplate
12
13
  from pydantic import (
13
14
  Field,
@@ -61,6 +62,9 @@ class ResourceTemplate(FastMCPComponent):
61
62
  parameters: dict[str, Any] = Field(
62
63
  description="JSON schema for function parameters"
63
64
  )
65
+ annotations: Annotations | None = Field(
66
+ default=None, description="Optional annotations about the resource's behavior"
67
+ )
64
68
 
65
69
  def __repr__(self) -> str:
66
70
  return f"{self.__class__.__name__}(uri_template={self.uri_template!r}, name={self.name!r}, description={self.description!r}, tags={self.tags})"
@@ -91,6 +95,8 @@ class ResourceTemplate(FastMCPComponent):
91
95
  mime_type: str | None = None,
92
96
  tags: set[str] | None = None,
93
97
  enabled: bool | None = None,
98
+ annotations: Annotations | None = None,
99
+ meta: dict[str, Any] | None = None,
94
100
  ) -> FunctionResourceTemplate:
95
101
  return FunctionResourceTemplate.from_function(
96
102
  fn=fn,
@@ -101,6 +107,8 @@ class ResourceTemplate(FastMCPComponent):
101
107
  mime_type=mime_type,
102
108
  tags=tags,
103
109
  enabled=enabled,
110
+ annotations=annotations,
111
+ meta=meta,
104
112
  )
105
113
 
106
114
  @field_validator("mime_type", mode="before")
@@ -139,7 +147,12 @@ class ResourceTemplate(FastMCPComponent):
139
147
  enabled=self.enabled,
140
148
  )
141
149
 
142
- def to_mcp_template(self, **overrides: Any) -> MCPResourceTemplate:
150
+ def to_mcp_template(
151
+ self,
152
+ *,
153
+ include_fastmcp_meta: bool | None = None,
154
+ **overrides: Any,
155
+ ) -> MCPResourceTemplate:
143
156
  """Convert the resource template to an MCPResourceTemplate."""
144
157
  kwargs = {
145
158
  "uriTemplate": self.uri_template,
@@ -147,6 +160,8 @@ class ResourceTemplate(FastMCPComponent):
147
160
  "description": self.description,
148
161
  "mimeType": self.mime_type,
149
162
  "title": self.title,
163
+ "annotations": self.annotations,
164
+ "_meta": self.get_meta(include_fastmcp_meta=include_fastmcp_meta),
150
165
  }
151
166
  return MCPResourceTemplate(**kwargs | overrides)
152
167
 
@@ -205,6 +220,8 @@ class FunctionResourceTemplate(ResourceTemplate):
205
220
  mime_type: str | None = None,
206
221
  tags: set[str] | None = None,
207
222
  enabled: bool | None = None,
223
+ annotations: Annotations | None = None,
224
+ meta: dict[str, Any] | None = None,
208
225
  ) -> FunctionResourceTemplate:
209
226
  """Create a template from a function."""
210
227
  from fastmcp.server.context import Context
@@ -289,4 +306,6 @@ class FunctionResourceTemplate(ResourceTemplate):
289
306
  parameters=parameters,
290
307
  tags=tags or set(),
291
308
  enabled=enabled if enabled is not None else True,
309
+ annotations=annotations,
310
+ meta=meta,
292
311
  )
@@ -1,4 +1,19 @@
1
- from .providers.bearer import BearerAuthProvider
1
+ from .auth import OAuthProvider, TokenVerifier
2
+ from .providers.jwt import JWTVerifier, StaticTokenVerifier
2
3
 
3
4
 
4
- __all__ = ["BearerAuthProvider"]
5
+ __all__ = [
6
+ "OAuthProvider",
7
+ "TokenVerifier",
8
+ "JWTVerifier",
9
+ "StaticTokenVerifier",
10
+ ]
11
+
12
+
13
+ def __getattr__(name: str):
14
+ # Defer import because it raises a deprecation warning
15
+ if name == "BearerAuthProvider":
16
+ from .providers.bearer import BearerAuthProvider
17
+
18
+ return BearerAuthProvider
19
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")