fastmcp 2.12.5__py3-none-any.whl → 2.13.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 (72) hide show
  1. fastmcp/cli/cli.py +7 -6
  2. fastmcp/cli/install/claude_code.py +6 -6
  3. fastmcp/cli/install/claude_desktop.py +3 -3
  4. fastmcp/cli/install/cursor.py +7 -7
  5. fastmcp/cli/install/gemini_cli.py +3 -3
  6. fastmcp/cli/install/mcp_json.py +3 -3
  7. fastmcp/cli/run.py +13 -8
  8. fastmcp/client/auth/oauth.py +100 -208
  9. fastmcp/client/client.py +11 -11
  10. fastmcp/client/logging.py +18 -14
  11. fastmcp/client/oauth_callback.py +85 -171
  12. fastmcp/client/transports.py +77 -22
  13. fastmcp/contrib/component_manager/component_service.py +6 -6
  14. fastmcp/contrib/mcp_mixin/README.md +32 -1
  15. fastmcp/contrib/mcp_mixin/mcp_mixin.py +14 -2
  16. fastmcp/experimental/utilities/openapi/json_schema_converter.py +4 -0
  17. fastmcp/experimental/utilities/openapi/parser.py +23 -3
  18. fastmcp/prompts/prompt.py +13 -6
  19. fastmcp/prompts/prompt_manager.py +16 -101
  20. fastmcp/resources/resource.py +13 -6
  21. fastmcp/resources/resource_manager.py +5 -164
  22. fastmcp/resources/template.py +107 -17
  23. fastmcp/resources/types.py +30 -24
  24. fastmcp/server/auth/auth.py +40 -32
  25. fastmcp/server/auth/handlers/authorize.py +324 -0
  26. fastmcp/server/auth/jwt_issuer.py +236 -0
  27. fastmcp/server/auth/middleware.py +96 -0
  28. fastmcp/server/auth/oauth_proxy.py +1256 -242
  29. fastmcp/server/auth/oidc_proxy.py +23 -6
  30. fastmcp/server/auth/providers/auth0.py +40 -21
  31. fastmcp/server/auth/providers/aws.py +29 -3
  32. fastmcp/server/auth/providers/azure.py +178 -127
  33. fastmcp/server/auth/providers/descope.py +4 -6
  34. fastmcp/server/auth/providers/github.py +29 -8
  35. fastmcp/server/auth/providers/google.py +30 -9
  36. fastmcp/server/auth/providers/introspection.py +281 -0
  37. fastmcp/server/auth/providers/jwt.py +8 -2
  38. fastmcp/server/auth/providers/scalekit.py +179 -0
  39. fastmcp/server/auth/providers/supabase.py +172 -0
  40. fastmcp/server/auth/providers/workos.py +32 -14
  41. fastmcp/server/context.py +122 -36
  42. fastmcp/server/http.py +58 -18
  43. fastmcp/server/low_level.py +121 -2
  44. fastmcp/server/middleware/caching.py +469 -0
  45. fastmcp/server/middleware/error_handling.py +6 -2
  46. fastmcp/server/middleware/logging.py +48 -37
  47. fastmcp/server/middleware/middleware.py +28 -15
  48. fastmcp/server/middleware/rate_limiting.py +3 -3
  49. fastmcp/server/middleware/tool_injection.py +116 -0
  50. fastmcp/server/proxy.py +6 -6
  51. fastmcp/server/server.py +683 -207
  52. fastmcp/settings.py +24 -10
  53. fastmcp/tools/tool.py +7 -3
  54. fastmcp/tools/tool_manager.py +30 -112
  55. fastmcp/tools/tool_transform.py +3 -3
  56. fastmcp/utilities/cli.py +62 -22
  57. fastmcp/utilities/components.py +5 -0
  58. fastmcp/utilities/inspect.py +77 -21
  59. fastmcp/utilities/logging.py +118 -8
  60. fastmcp/utilities/mcp_server_config/v1/environments/uv.py +6 -6
  61. fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +3 -3
  62. fastmcp/utilities/mcp_server_config/v1/schema.json +3 -0
  63. fastmcp/utilities/tests.py +87 -4
  64. fastmcp/utilities/types.py +1 -1
  65. fastmcp/utilities/ui.py +617 -0
  66. {fastmcp-2.12.5.dist-info → fastmcp-2.13.0.dist-info}/METADATA +10 -6
  67. {fastmcp-2.12.5.dist-info → fastmcp-2.13.0.dist-info}/RECORD +70 -63
  68. fastmcp/cli/claude.py +0 -135
  69. fastmcp/utilities/storage.py +0 -204
  70. {fastmcp-2.12.5.dist-info → fastmcp-2.13.0.dist-info}/WHEEL +0 -0
  71. {fastmcp-2.12.5.dist-info → fastmcp-2.13.0.dist-info}/entry_points.txt +0 -0
  72. {fastmcp-2.12.5.dist-info → fastmcp-2.13.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
- from collections.abc import Awaitable
4
+ from collections.abc import Awaitable, Sequence
5
5
  from dataclasses import dataclass, field, replace
6
6
  from datetime import datetime, timezone
7
7
  from functools import partial
@@ -99,6 +99,8 @@ class Middleware:
99
99
  handler = call_next
100
100
 
101
101
  match context.method:
102
+ case "initialize":
103
+ handler = partial(self.on_initialize, call_next=handler)
102
104
  case "tools/call":
103
105
  handler = partial(self.on_call_tool, call_next=handler)
104
106
  case "resources/read":
@@ -133,18 +135,25 @@ class Middleware:
133
135
 
134
136
  async def on_request(
135
137
  self,
136
- context: MiddlewareContext[mt.Request],
137
- call_next: CallNext[mt.Request, Any],
138
+ context: MiddlewareContext[mt.Request[Any, Any]],
139
+ call_next: CallNext[mt.Request[Any, Any], Any],
138
140
  ) -> Any:
139
141
  return await call_next(context)
140
142
 
141
143
  async def on_notification(
142
144
  self,
143
- context: MiddlewareContext[mt.Notification],
144
- call_next: CallNext[mt.Notification, Any],
145
+ context: MiddlewareContext[mt.Notification[Any, Any]],
146
+ call_next: CallNext[mt.Notification[Any, Any], Any],
145
147
  ) -> Any:
146
148
  return await call_next(context)
147
149
 
150
+ async def on_initialize(
151
+ self,
152
+ context: MiddlewareContext[mt.InitializeRequestParams],
153
+ call_next: CallNext[mt.InitializeRequestParams, None],
154
+ ) -> None:
155
+ return await call_next(context)
156
+
148
157
  async def on_call_tool(
149
158
  self,
150
159
  context: MiddlewareContext[mt.CallToolRequestParams],
@@ -155,8 +164,10 @@ class Middleware:
155
164
  async def on_read_resource(
156
165
  self,
157
166
  context: MiddlewareContext[mt.ReadResourceRequestParams],
158
- call_next: CallNext[mt.ReadResourceRequestParams, list[ReadResourceContents]],
159
- ) -> list[ReadResourceContents]:
167
+ call_next: CallNext[
168
+ mt.ReadResourceRequestParams, Sequence[ReadResourceContents]
169
+ ],
170
+ ) -> Sequence[ReadResourceContents]:
160
171
  return await call_next(context)
161
172
 
162
173
  async def on_get_prompt(
@@ -169,27 +180,29 @@ class Middleware:
169
180
  async def on_list_tools(
170
181
  self,
171
182
  context: MiddlewareContext[mt.ListToolsRequest],
172
- call_next: CallNext[mt.ListToolsRequest, list[Tool]],
173
- ) -> list[Tool]:
183
+ call_next: CallNext[mt.ListToolsRequest, Sequence[Tool]],
184
+ ) -> Sequence[Tool]:
174
185
  return await call_next(context)
175
186
 
176
187
  async def on_list_resources(
177
188
  self,
178
189
  context: MiddlewareContext[mt.ListResourcesRequest],
179
- call_next: CallNext[mt.ListResourcesRequest, list[Resource]],
180
- ) -> list[Resource]:
190
+ call_next: CallNext[mt.ListResourcesRequest, Sequence[Resource]],
191
+ ) -> Sequence[Resource]:
181
192
  return await call_next(context)
182
193
 
183
194
  async def on_list_resource_templates(
184
195
  self,
185
196
  context: MiddlewareContext[mt.ListResourceTemplatesRequest],
186
- call_next: CallNext[mt.ListResourceTemplatesRequest, list[ResourceTemplate]],
187
- ) -> list[ResourceTemplate]:
197
+ call_next: CallNext[
198
+ mt.ListResourceTemplatesRequest, Sequence[ResourceTemplate]
199
+ ],
200
+ ) -> Sequence[ResourceTemplate]:
188
201
  return await call_next(context)
189
202
 
190
203
  async def on_list_prompts(
191
204
  self,
192
205
  context: MiddlewareContext[mt.ListPromptsRequest],
193
- call_next: CallNext[mt.ListPromptsRequest, list[Prompt]],
194
- ) -> list[Prompt]:
206
+ call_next: CallNext[mt.ListPromptsRequest, Sequence[Prompt]],
207
+ ) -> Sequence[Prompt]:
195
208
  return await call_next(context)
@@ -1,11 +1,11 @@
1
1
  """Rate limiting middleware for protecting FastMCP servers from abuse."""
2
2
 
3
- import asyncio
4
3
  import time
5
4
  from collections import defaultdict, deque
6
5
  from collections.abc import Callable
7
6
  from typing import Any
8
7
 
8
+ import anyio
9
9
  from mcp import McpError
10
10
  from mcp.types import ErrorData
11
11
 
@@ -33,7 +33,7 @@ class TokenBucketRateLimiter:
33
33
  self.refill_rate = refill_rate
34
34
  self.tokens = capacity
35
35
  self.last_refill = time.time()
36
- self._lock = asyncio.Lock()
36
+ self._lock = anyio.Lock()
37
37
 
38
38
  async def consume(self, tokens: int = 1) -> bool:
39
39
  """Try to consume tokens from the bucket.
@@ -71,7 +71,7 @@ class SlidingWindowRateLimiter:
71
71
  self.max_requests = max_requests
72
72
  self.window_seconds = window_seconds
73
73
  self.requests = deque()
74
- self._lock = asyncio.Lock()
74
+ self._lock = anyio.Lock()
75
75
 
76
76
  async def is_allowed(self) -> bool:
77
77
  """Check if a request is allowed."""
@@ -0,0 +1,116 @@
1
+ """A middleware for injecting tools into the MCP server context."""
2
+
3
+ from collections.abc import Sequence
4
+ from logging import Logger
5
+ from typing import Annotated, Any
6
+
7
+ import mcp.types
8
+ from mcp.server.lowlevel.helper_types import ReadResourceContents
9
+ from mcp.types import Prompt
10
+ from pydantic import AnyUrl
11
+ from typing_extensions import override
12
+
13
+ from fastmcp.server.context import Context
14
+ from fastmcp.server.middleware.middleware import CallNext, Middleware, MiddlewareContext
15
+ from fastmcp.tools.tool import Tool, ToolResult
16
+ from fastmcp.utilities.logging import get_logger
17
+
18
+ logger: Logger = get_logger(name=__name__)
19
+
20
+
21
+ class ToolInjectionMiddleware(Middleware):
22
+ """A middleware for injecting tools into the context."""
23
+
24
+ def __init__(self, tools: Sequence[Tool]):
25
+ """Initialize the tool injection middleware."""
26
+ self._tools_to_inject: Sequence[Tool] = tools
27
+ self._tools_to_inject_by_name: dict[str, Tool] = {
28
+ tool.name: tool for tool in tools
29
+ }
30
+
31
+ @override
32
+ async def on_list_tools(
33
+ self,
34
+ context: MiddlewareContext[mcp.types.ListToolsRequest],
35
+ call_next: CallNext[mcp.types.ListToolsRequest, Sequence[Tool]],
36
+ ) -> Sequence[Tool]:
37
+ """Inject tools into the response."""
38
+ return [*self._tools_to_inject, *await call_next(context)]
39
+
40
+ @override
41
+ async def on_call_tool(
42
+ self,
43
+ context: MiddlewareContext[mcp.types.CallToolRequestParams],
44
+ call_next: CallNext[mcp.types.CallToolRequestParams, ToolResult],
45
+ ) -> ToolResult:
46
+ """Intercept tool calls to injected tools."""
47
+ if context.message.name in self._tools_to_inject_by_name:
48
+ tool = self._tools_to_inject_by_name[context.message.name]
49
+ return await tool.run(arguments=context.message.arguments or {})
50
+
51
+ return await call_next(context)
52
+
53
+
54
+ async def list_prompts(context: Context) -> list[Prompt]:
55
+ """List prompts available on the server."""
56
+ return await context.list_prompts()
57
+
58
+
59
+ list_prompts_tool = Tool.from_function(
60
+ fn=list_prompts,
61
+ )
62
+
63
+
64
+ async def get_prompt(
65
+ context: Context,
66
+ name: Annotated[str, "The name of the prompt to render."],
67
+ arguments: Annotated[
68
+ dict[str, Any] | None, "The arguments to pass to the prompt."
69
+ ] = None,
70
+ ) -> mcp.types.GetPromptResult:
71
+ """Render a prompt available on the server."""
72
+ return await context.get_prompt(name=name, arguments=arguments)
73
+
74
+
75
+ get_prompt_tool = Tool.from_function(
76
+ fn=get_prompt,
77
+ )
78
+
79
+
80
+ class PromptToolMiddleware(ToolInjectionMiddleware):
81
+ """A middleware for injecting prompts as tools into the context."""
82
+
83
+ def __init__(self) -> None:
84
+ tools: list[Tool] = [list_prompts_tool, get_prompt_tool]
85
+ super().__init__(tools=tools)
86
+
87
+
88
+ async def list_resources(context: Context) -> list[mcp.types.Resource]:
89
+ """List resources available on the server."""
90
+ return await context.list_resources()
91
+
92
+
93
+ list_resources_tool = Tool.from_function(
94
+ fn=list_resources,
95
+ )
96
+
97
+
98
+ async def read_resource(
99
+ context: Context,
100
+ uri: Annotated[AnyUrl | str, "The URI of the resource to read."],
101
+ ) -> list[ReadResourceContents]:
102
+ """Read a resource available on the server."""
103
+ return await context.read_resource(uri=uri)
104
+
105
+
106
+ read_resource_tool = Tool.from_function(
107
+ fn=read_resource,
108
+ )
109
+
110
+
111
+ class ResourceToolMiddleware(ToolInjectionMiddleware):
112
+ """A middleware for injecting resources as tools into the context."""
113
+
114
+ def __init__(self) -> None:
115
+ tools: list[Tool] = [list_resources_tool, read_resource_tool]
116
+ super().__init__(tools=tools)
fastmcp/server/proxy.py CHANGED
@@ -69,7 +69,7 @@ class ProxyManagerMixin:
69
69
  class ProxyToolManager(ToolManager, ProxyManagerMixin):
70
70
  """A ToolManager that sources its tools from a remote client in addition to local and mounted tools."""
71
71
 
72
- def __init__(self, client_factory: ClientFactoryT, **kwargs):
72
+ def __init__(self, client_factory: ClientFactoryT, **kwargs: Any):
73
73
  super().__init__(**kwargs)
74
74
  self.client_factory = client_factory
75
75
 
@@ -123,7 +123,7 @@ class ProxyToolManager(ToolManager, ProxyManagerMixin):
123
123
  class ProxyResourceManager(ResourceManager, ProxyManagerMixin):
124
124
  """A ResourceManager that sources its resources from a remote client in addition to local and mounted resources."""
125
125
 
126
- def __init__(self, client_factory: ClientFactoryT, **kwargs):
126
+ def __init__(self, client_factory: ClientFactoryT, **kwargs: Any):
127
127
  super().__init__(**kwargs)
128
128
  self.client_factory = client_factory
129
129
 
@@ -204,7 +204,7 @@ class ProxyResourceManager(ResourceManager, ProxyManagerMixin):
204
204
  class ProxyPromptManager(PromptManager, ProxyManagerMixin):
205
205
  """A PromptManager that sources its prompts from a remote client in addition to local and mounted prompts."""
206
206
 
207
- def __init__(self, client_factory: ClientFactoryT, **kwargs):
207
+ def __init__(self, client_factory: ClientFactoryT, **kwargs: Any):
208
208
  super().__init__(**kwargs)
209
209
  self.client_factory = client_factory
210
210
 
@@ -258,7 +258,7 @@ class ProxyTool(Tool, MirroredComponent):
258
258
  A Tool that represents and executes a tool on a remote server.
259
259
  """
260
260
 
261
- def __init__(self, client: Client, **kwargs):
261
+ def __init__(self, client: Client, **kwargs: Any):
262
262
  super().__init__(**kwargs)
263
263
  self._client = client
264
264
 
@@ -354,7 +354,7 @@ class ProxyTemplate(ResourceTemplate, MirroredComponent):
354
354
  A ResourceTemplate that represents and creates resources from a remote server template.
355
355
  """
356
356
 
357
- def __init__(self, client: Client, **kwargs):
357
+ def __init__(self, client: Client, **kwargs: Any):
358
358
  super().__init__(**kwargs)
359
359
  self._client = client
360
360
 
@@ -640,7 +640,7 @@ class StatefulProxyClient(ProxyClient[ClientTransportT]):
640
640
  Note that it is essential to ensure that the proxy server itself is also stateful.
641
641
  """
642
642
 
643
- def __init__(self, *args, **kwargs):
643
+ def __init__(self, *args: Any, **kwargs: Any):
644
644
  super().__init__(*args, **kwargs)
645
645
  self._caches: dict[ServerSession, Client[ClientTransportT]] = {}
646
646