fastmcp 2.10.6__py3-none-any.whl → 2.11.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.
Files changed (61) hide show
  1. fastmcp/cli/cli.py +128 -33
  2. fastmcp/cli/install/claude_code.py +42 -1
  3. fastmcp/cli/install/claude_desktop.py +42 -1
  4. fastmcp/cli/install/cursor.py +42 -1
  5. fastmcp/cli/install/mcp_json.py +41 -0
  6. fastmcp/cli/run.py +127 -1
  7. fastmcp/client/__init__.py +2 -0
  8. fastmcp/client/auth/oauth.py +68 -99
  9. fastmcp/client/oauth_callback.py +18 -0
  10. fastmcp/client/transports.py +69 -15
  11. fastmcp/contrib/component_manager/example.py +2 -2
  12. fastmcp/experimental/server/openapi/README.md +266 -0
  13. fastmcp/experimental/server/openapi/__init__.py +38 -0
  14. fastmcp/experimental/server/openapi/components.py +348 -0
  15. fastmcp/experimental/server/openapi/routing.py +132 -0
  16. fastmcp/experimental/server/openapi/server.py +466 -0
  17. fastmcp/experimental/utilities/openapi/README.md +239 -0
  18. fastmcp/experimental/utilities/openapi/__init__.py +68 -0
  19. fastmcp/experimental/utilities/openapi/director.py +208 -0
  20. fastmcp/experimental/utilities/openapi/formatters.py +355 -0
  21. fastmcp/experimental/utilities/openapi/json_schema_converter.py +340 -0
  22. fastmcp/experimental/utilities/openapi/models.py +85 -0
  23. fastmcp/experimental/utilities/openapi/parser.py +618 -0
  24. fastmcp/experimental/utilities/openapi/schemas.py +538 -0
  25. fastmcp/mcp_config.py +125 -88
  26. fastmcp/prompts/prompt.py +11 -1
  27. fastmcp/resources/resource.py +21 -1
  28. fastmcp/resources/template.py +20 -1
  29. fastmcp/server/auth/__init__.py +18 -2
  30. fastmcp/server/auth/auth.py +225 -7
  31. fastmcp/server/auth/providers/bearer.py +25 -473
  32. fastmcp/server/auth/providers/in_memory.py +4 -2
  33. fastmcp/server/auth/providers/jwt.py +538 -0
  34. fastmcp/server/auth/providers/workos.py +151 -0
  35. fastmcp/server/auth/registry.py +52 -0
  36. fastmcp/server/context.py +107 -26
  37. fastmcp/server/dependencies.py +9 -2
  38. fastmcp/server/http.py +48 -57
  39. fastmcp/server/middleware/middleware.py +3 -23
  40. fastmcp/server/openapi.py +1 -1
  41. fastmcp/server/proxy.py +50 -11
  42. fastmcp/server/server.py +168 -59
  43. fastmcp/settings.py +73 -6
  44. fastmcp/tools/tool.py +36 -3
  45. fastmcp/tools/tool_manager.py +38 -2
  46. fastmcp/tools/tool_transform.py +112 -3
  47. fastmcp/utilities/components.py +41 -3
  48. fastmcp/utilities/json_schema.py +136 -98
  49. fastmcp/utilities/json_schema_type.py +1 -3
  50. fastmcp/utilities/mcp_config.py +28 -0
  51. fastmcp/utilities/openapi.py +243 -57
  52. fastmcp/utilities/tests.py +54 -6
  53. fastmcp/utilities/types.py +94 -11
  54. {fastmcp-2.10.6.dist-info → fastmcp-2.11.1.dist-info}/METADATA +4 -3
  55. fastmcp-2.11.1.dist-info/RECORD +108 -0
  56. fastmcp/server/auth/providers/bearer_env.py +0 -63
  57. fastmcp/utilities/cache.py +0 -26
  58. fastmcp-2.10.6.dist-info/RECORD +0 -93
  59. {fastmcp-2.10.6.dist-info → fastmcp-2.11.1.dist-info}/WHEEL +0 -0
  60. {fastmcp-2.10.6.dist-info → fastmcp-2.11.1.dist-info}/entry_points.txt +0 -0
  61. {fastmcp-2.10.6.dist-info → fastmcp-2.11.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,52 @@
1
+ """Provider registry for FastMCP auth providers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from typing import TYPE_CHECKING, TypeVar
7
+
8
+ if TYPE_CHECKING:
9
+ from fastmcp.server.auth.auth import AuthProvider
10
+
11
+ # Type variable for auth providers
12
+ T = TypeVar("T", bound="AuthProvider")
13
+
14
+
15
+ # Provider Registry
16
+ _PROVIDER_REGISTRY: dict[str, type[AuthProvider]] = {}
17
+
18
+
19
+ def register_provider(name: str) -> Callable[[type[T]], type[T]]:
20
+ """Decorator to register an auth provider with a given name.
21
+
22
+ Args:
23
+ name: The name to register the provider under (e.g., 'AUTHKIT')
24
+
25
+ Returns:
26
+ The decorated class
27
+
28
+ Example:
29
+ @register_provider('AUTHKIT')
30
+ class AuthKitProvider(AuthProvider):
31
+ ...
32
+ """
33
+
34
+ def decorator(cls: type[T]) -> type[T]:
35
+ _PROVIDER_REGISTRY[name.upper()] = cls
36
+ return cls
37
+
38
+ return decorator
39
+
40
+
41
+ def get_registered_provider(name: str) -> type[AuthProvider]:
42
+ """Get a registered provider by name.
43
+
44
+ Args:
45
+ name: The provider name (case-insensitive)
46
+
47
+ Returns:
48
+ The provider class if found, None otherwise
49
+ """
50
+ if name.upper() in _PROVIDER_REGISTRY:
51
+ return _PROVIDER_REGISTRY[name.upper()]
52
+ raise ValueError(f"Provider {name!r} has not been registered.")
fastmcp/server/context.py CHANGED
@@ -1,8 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import copy
4
5
  import warnings
5
- from collections.abc import Generator
6
+ from collections.abc import Generator, Mapping
6
7
  from contextlib import contextmanager
7
8
  from contextvars import ContextVar, Token
8
9
  from dataclasses import dataclass
@@ -46,6 +47,18 @@ _current_context: ContextVar[Context | None] = ContextVar("context", default=Non
46
47
  _flush_lock = asyncio.Lock()
47
48
 
48
49
 
50
+ @dataclass
51
+ class LogData:
52
+ """Data object for passing log arguments to client-side handlers.
53
+
54
+ This provides an interface to match the Python standard library logging,
55
+ for compatibility with structured logging.
56
+ """
57
+
58
+ msg: str
59
+ extra: Mapping[str, Any] | None = None
60
+
61
+
49
62
  @contextmanager
50
63
  def set_context(context: Context) -> Generator[Context, None, None]:
51
64
  token = _current_context.set(context)
@@ -83,9 +96,19 @@ class Context:
83
96
  request_id = ctx.request_id
84
97
  client_id = ctx.client_id
85
98
 
99
+ # Manage state across the request
100
+ ctx.set_state("key", "value")
101
+ value = ctx.get_state("key")
102
+
86
103
  return str(x)
87
104
  ```
88
105
 
106
+ State Management:
107
+ Context objects maintain a state dictionary that can be used to store and share
108
+ data across middleware and tool calls within a request. When a new context
109
+ is created (nested contexts), it inherits a copy of its parent's state, ensuring
110
+ that modifications in child contexts don't affect parent contexts.
111
+
89
112
  The context parameter name can be anything as long as it's annotated with Context.
90
113
  The context is optional - tools that don't need it can omit the parameter.
91
114
 
@@ -95,9 +118,15 @@ class Context:
95
118
  self.fastmcp = fastmcp
96
119
  self._tokens: list[Token] = []
97
120
  self._notification_queue: set[str] = set() # Dedupe notifications
121
+ self._state: dict[str, Any] = {}
98
122
 
99
123
  async def __aenter__(self) -> Context:
100
124
  """Enter the context manager and set this context as the current context."""
125
+ parent_context = _current_context.get(None)
126
+ if parent_context is not None:
127
+ # Inherit state from parent context
128
+ self._state = copy.deepcopy(parent_context._state)
129
+
101
130
  # Always set this context and save the token
102
131
  token = _current_context.set(self)
103
132
  self._tokens.append(token)
@@ -113,7 +142,7 @@ class Context:
113
142
  _current_context.reset(token)
114
143
 
115
144
  @property
116
- def request_context(self) -> RequestContext:
145
+ def request_context(self) -> RequestContext[ServerSession, Any, Request]:
117
146
  """Access to the underlying request context.
118
147
 
119
148
  If called outside of a request context, this will raise a ValueError.
@@ -167,6 +196,7 @@ class Context:
167
196
  message: str,
168
197
  level: LoggingLevel | None = None,
169
198
  logger_name: str | None = None,
199
+ extra: Mapping[str, Any] | None = None,
170
200
  ) -> None:
171
201
  """Send a log message to the client.
172
202
 
@@ -175,12 +205,14 @@ class Context:
175
205
  level: Optional log level. One of "debug", "info", "notice", "warning", "error", "critical",
176
206
  "alert", or "emergency". Default is "info".
177
207
  logger_name: Optional logger name
208
+ extra: Optional mapping for additional arguments
178
209
  """
179
210
  if level is None:
180
211
  level = "info"
212
+ data = LogData(msg=message, extra=extra)
181
213
  await self.session.send_log_message(
182
214
  level=level,
183
- data=message,
215
+ data=data,
184
216
  logger=logger_name,
185
217
  related_request_id=self.request_id,
186
218
  )
@@ -200,35 +232,48 @@ class Context:
200
232
  return str(self.request_context.request_id)
201
233
 
202
234
  @property
203
- def session_id(self) -> str | None:
204
- """Get the MCP session ID for HTTP transports.
235
+ def session_id(self) -> str:
236
+ """Get the MCP session ID for ALL transports.
205
237
 
206
238
  Returns the session ID that can be used as a key for session-based
207
239
  data storage (e.g., Redis) to share data between tool calls within
208
240
  the same client session.
209
241
 
210
242
  Returns:
211
- The session ID for HTTP transports (SSE, StreamableHTTP), or None
212
- for stdio and in-memory transports which don't use session IDs.
243
+ The session ID for StreamableHTTP transports, or a generated ID
244
+ for other transports.
213
245
 
214
246
  Example:
215
247
  ```python
216
248
  @server.tool
217
249
  def store_data(data: dict, ctx: Context) -> str:
218
- if session_id := ctx.session_id:
219
- redis_client.set(f"session:{session_id}:data", json.dumps(data))
220
- return f"Data stored for session {session_id}"
221
- return "No session ID available (stdio/memory transport)"
250
+ session_id = ctx.session_id
251
+ redis_client.set(f"session:{session_id}:data", json.dumps(data))
252
+ return f"Data stored for session {session_id}"
222
253
  ```
223
254
  """
224
- try:
225
- from fastmcp.server.dependencies import get_http_headers
255
+ request_ctx = self.request_context
256
+ session = request_ctx.session
226
257
 
227
- headers = get_http_headers(include_all=True)
228
- return headers.get("mcp-session-id")
229
- except RuntimeError:
230
- # No HTTP context available (stdio/in-memory transport)
231
- return None
258
+ # Try to get the session ID from the session attributes
259
+ session_id = getattr(session, "_fastmcp_id", None)
260
+ if session_id is not None:
261
+ return session_id
262
+
263
+ # Try to get the session ID from the http request headers
264
+ request = request_ctx.request
265
+ if request:
266
+ session_id = request.headers.get("mcp-session-id")
267
+
268
+ # Generate a session ID if it doesn't exist.
269
+ if session_id is None:
270
+ from uuid import uuid4
271
+
272
+ session_id = str(uuid4())
273
+
274
+ # Save the session id to the session attributes
275
+ setattr(session, "_fastmcp_id", session_id)
276
+ return session_id
232
277
 
233
278
  @property
234
279
  def session(self) -> ServerSession:
@@ -236,21 +281,49 @@ class Context:
236
281
  return self.request_context.session
237
282
 
238
283
  # Convenience methods for common log levels
239
- async def debug(self, message: str, logger_name: str | None = None) -> None:
284
+ async def debug(
285
+ self,
286
+ message: str,
287
+ logger_name: str | None = None,
288
+ extra: Mapping[str, Any] | None = None,
289
+ ) -> None:
240
290
  """Send a debug log message."""
241
- await self.log(level="debug", message=message, logger_name=logger_name)
291
+ await self.log(
292
+ level="debug", message=message, logger_name=logger_name, extra=extra
293
+ )
242
294
 
243
- async def info(self, message: str, logger_name: str | None = None) -> None:
295
+ async def info(
296
+ self,
297
+ message: str,
298
+ logger_name: str | None = None,
299
+ extra: Mapping[str, Any] | None = None,
300
+ ) -> None:
244
301
  """Send an info log message."""
245
- await self.log(level="info", message=message, logger_name=logger_name)
302
+ await self.log(
303
+ level="info", message=message, logger_name=logger_name, extra=extra
304
+ )
246
305
 
247
- async def warning(self, message: str, logger_name: str | None = None) -> None:
306
+ async def warning(
307
+ self,
308
+ message: str,
309
+ logger_name: str | None = None,
310
+ extra: Mapping[str, Any] | None = None,
311
+ ) -> None:
248
312
  """Send a warning log message."""
249
- await self.log(level="warning", message=message, logger_name=logger_name)
313
+ await self.log(
314
+ level="warning", message=message, logger_name=logger_name, extra=extra
315
+ )
250
316
 
251
- async def error(self, message: str, logger_name: str | None = None) -> None:
317
+ async def error(
318
+ self,
319
+ message: str,
320
+ logger_name: str | None = None,
321
+ extra: Mapping[str, Any] | None = None,
322
+ ) -> None:
252
323
  """Send an error log message."""
253
- await self.log(level="error", message=message, logger_name=logger_name)
324
+ await self.log(
325
+ level="error", message=message, logger_name=logger_name, extra=extra
326
+ )
254
327
 
255
328
  async def list_roots(self) -> list[Root]:
256
329
  """List the roots available to the server, as indicated by the client."""
@@ -455,6 +528,14 @@ class Context:
455
528
 
456
529
  return fastmcp.server.dependencies.get_http_request()
457
530
 
531
+ def set_state(self, key: str, value: Any) -> None:
532
+ """Set a value in the context state."""
533
+ self._state[key] = value
534
+
535
+ def get_state(self, key: str) -> Any:
536
+ """Get a value from the context state. Returns None if the key is not found."""
537
+ return self._state.get(key)
538
+
458
539
  def _queue_tool_list_changed(self) -> None:
459
540
  """Queue a tool list changed notification."""
460
541
  self._notification_queue.add("notifications/tools/list_changed")
@@ -37,9 +37,14 @@ def get_context() -> Context:
37
37
 
38
38
 
39
39
  def get_http_request() -> Request:
40
- from fastmcp.server.http import _current_http_request
40
+ from mcp.server.lowlevel.server import request_ctx
41
+
42
+ request = None
43
+ try:
44
+ request = request_ctx.get().request
45
+ except LookupError:
46
+ pass
41
47
 
42
- request = _current_http_request.get()
43
48
  if request is None:
44
49
  raise RuntimeError("No active HTTP request found.")
45
50
  return request
@@ -72,6 +77,8 @@ def get_http_headers(include_all: bool = False) -> dict[str, str]:
72
77
  "proxy-authenticate",
73
78
  "proxy-authorization",
74
79
  "proxy-connection",
80
+ # MCP-related headers
81
+ "mcp-session-id",
75
82
  }
76
83
  # (just in case)
77
84
  if not all(h.lower() == h for h in exclude_headers):
fastmcp/server/http.py CHANGED
@@ -3,14 +3,14 @@ from __future__ import annotations
3
3
  from collections.abc import AsyncGenerator, Callable, Generator
4
4
  from contextlib import asynccontextmanager, contextmanager
5
5
  from contextvars import ContextVar
6
- from typing import TYPE_CHECKING
6
+ from typing import TYPE_CHECKING, cast
7
7
 
8
8
  from mcp.server.auth.middleware.auth_context import AuthContextMiddleware
9
9
  from mcp.server.auth.middleware.bearer_auth import (
10
10
  BearerAuthBackend,
11
11
  RequireAuthMiddleware,
12
12
  )
13
- from mcp.server.auth.routes import create_auth_routes
13
+ from mcp.server.auth.provider import TokenVerifier as TokenVerifierProtocol
14
14
  from mcp.server.lowlevel.server import LifespanResultT
15
15
  from mcp.server.sse import SseServerTransport
16
16
  from mcp.server.streamable_http import EventStore
@@ -23,7 +23,7 @@ from starlette.responses import Response
23
23
  from starlette.routing import BaseRoute, Mount, Route
24
24
  from starlette.types import Lifespan, Receive, Scope, Send
25
25
 
26
- from fastmcp.server.auth.auth import OAuthProvider
26
+ from fastmcp.server.auth.auth import AuthProvider
27
27
  from fastmcp.utilities.logging import get_logger
28
28
 
29
29
  if TYPE_CHECKING:
@@ -69,44 +69,6 @@ class RequestContextMiddleware:
69
69
  await self.app(scope, receive, send)
70
70
 
71
71
 
72
- def setup_auth_middleware_and_routes(
73
- auth: OAuthProvider,
74
- ) -> tuple[list[Middleware], list[BaseRoute], list[str]]:
75
- """Set up authentication middleware and routes if auth is enabled.
76
-
77
- Args:
78
- auth: The OAuthProvider authorization server provider
79
-
80
- Returns:
81
- Tuple of (middleware, auth_routes, required_scopes)
82
- """
83
- middleware: list[Middleware] = []
84
- auth_routes: list[BaseRoute] = []
85
- required_scopes: list[str] = []
86
-
87
- middleware = [
88
- Middleware(
89
- AuthenticationMiddleware,
90
- backend=BearerAuthBackend(auth),
91
- ),
92
- Middleware(AuthContextMiddleware),
93
- ]
94
-
95
- required_scopes = auth.required_scopes or []
96
-
97
- auth_routes.extend(
98
- create_auth_routes(
99
- provider=auth,
100
- issuer_url=auth.issuer_url,
101
- service_documentation_url=auth.service_documentation_url,
102
- client_registration_options=auth.client_registration_options,
103
- revocation_options=auth.revocation_options,
104
- )
105
- )
106
-
107
- return middleware, auth_routes, required_scopes
108
-
109
-
110
72
  def create_base_app(
111
73
  routes: list[BaseRoute],
112
74
  middleware: list[Middleware],
@@ -139,7 +101,7 @@ def create_sse_app(
139
101
  server: FastMCP[LifespanResultT],
140
102
  message_path: str,
141
103
  sse_path: str,
142
- auth: OAuthProvider | None = None,
104
+ auth: AuthProvider | None = None,
143
105
  debug: bool = False,
144
106
  routes: list[BaseRoute] | None = None,
145
107
  middleware: list[Middleware] | None = None,
@@ -150,7 +112,7 @@ def create_sse_app(
150
112
  server: The FastMCP server instance
151
113
  message_path: Path for SSE messages
152
114
  sse_path: Path for SSE connections
153
- auth: Optional auth provider
115
+ auth: Optional authentication provider (AuthProvider)
154
116
  debug: Whether to enable debug mode
155
117
  routes: Optional list of custom routes
156
118
  middleware: Optional list of middleware
@@ -174,28 +136,43 @@ def create_sse_app(
174
136
  )
175
137
  return Response()
176
138
 
177
- # Get auth middleware and routes
178
-
179
- # Add SSE routes with or without auth
139
+ # Set up auth if enabled
180
140
  if auth:
181
- auth_middleware, auth_routes, required_scopes = (
182
- setup_auth_middleware_and_routes(auth)
183
- )
141
+ # Create auth middleware
142
+ auth_middleware = [
143
+ Middleware(
144
+ AuthenticationMiddleware,
145
+ backend=BearerAuthBackend(auth),
146
+ ),
147
+ Middleware(AuthContextMiddleware),
148
+ ]
149
+
150
+ # Get auth routes and scopes
151
+ auth_routes = auth.get_routes()
152
+ required_scopes = getattr(auth, "required_scopes", None) or []
153
+
154
+ # Get resource metadata URL for WWW-Authenticate header
155
+ resource_metadata_url = auth.get_resource_metadata_url()
184
156
 
185
157
  server_routes.extend(auth_routes)
186
158
  server_middleware.extend(auth_middleware)
159
+
187
160
  # Auth is enabled, wrap endpoints with RequireAuthMiddleware
188
161
  server_routes.append(
189
162
  Route(
190
163
  sse_path,
191
- endpoint=RequireAuthMiddleware(handle_sse, required_scopes),
164
+ endpoint=RequireAuthMiddleware(
165
+ handle_sse, required_scopes, resource_metadata_url
166
+ ),
192
167
  methods=["GET"],
193
168
  )
194
169
  )
195
170
  server_routes.append(
196
171
  Mount(
197
172
  message_path,
198
- app=RequireAuthMiddleware(sse.handle_post_message, required_scopes),
173
+ app=RequireAuthMiddleware(
174
+ sse.handle_post_message, required_scopes, resource_metadata_url
175
+ ),
199
176
  )
200
177
  )
201
178
  else:
@@ -243,7 +220,7 @@ def create_streamable_http_app(
243
220
  server: FastMCP[LifespanResultT],
244
221
  streamable_http_path: str,
245
222
  event_store: EventStore | None = None,
246
- auth: OAuthProvider | None = None,
223
+ auth: AuthProvider | None = None,
247
224
  json_response: bool = False,
248
225
  stateless_http: bool = False,
249
226
  debug: bool = False,
@@ -256,7 +233,7 @@ def create_streamable_http_app(
256
233
  server: The FastMCP server instance
257
234
  streamable_http_path: Path for StreamableHTTP connections
258
235
  event_store: Optional event store for session management
259
- auth: Optional auth provider
236
+ auth: Optional authentication provider (AuthProvider)
260
237
  json_response: Whether to use JSON response format
261
238
  stateless_http: Whether to use stateless mode (new transport per request)
262
239
  debug: Whether to enable debug mode
@@ -307,9 +284,21 @@ def create_streamable_http_app(
307
284
 
308
285
  # Add StreamableHTTP routes with or without auth
309
286
  if auth:
310
- auth_middleware, auth_routes, required_scopes = (
311
- setup_auth_middleware_and_routes(auth)
312
- )
287
+ # Create auth middleware
288
+ auth_middleware = [
289
+ Middleware(
290
+ AuthenticationMiddleware,
291
+ backend=BearerAuthBackend(cast(TokenVerifierProtocol, auth)),
292
+ ),
293
+ Middleware(AuthContextMiddleware),
294
+ ]
295
+
296
+ # Get auth routes and scopes
297
+ auth_routes = auth.get_routes()
298
+ required_scopes = getattr(auth, "required_scopes", None) or []
299
+
300
+ # Get resource metadata URL for WWW-Authenticate header
301
+ resource_metadata_url = auth.get_resource_metadata_url()
313
302
 
314
303
  server_routes.extend(auth_routes)
315
304
  server_middleware.extend(auth_middleware)
@@ -318,7 +307,9 @@ def create_streamable_http_app(
318
307
  server_routes.append(
319
308
  Mount(
320
309
  streamable_http_path,
321
- app=RequireAuthMiddleware(handle_streamable_http, required_scopes),
310
+ app=RequireAuthMiddleware(
311
+ handle_streamable_http, required_scopes, resource_metadata_url
312
+ ),
322
313
  )
323
314
  )
324
315
  else:
@@ -20,7 +20,7 @@ import mcp.types as mt
20
20
  from fastmcp.prompts.prompt import Prompt
21
21
  from fastmcp.resources.resource import Resource
22
22
  from fastmcp.resources.template import ResourceTemplate
23
- from fastmcp.tools.tool import Tool
23
+ from fastmcp.tools.tool import Tool, ToolResult
24
24
 
25
25
  if TYPE_CHECKING:
26
26
  from fastmcp.server.context import Context
@@ -43,26 +43,6 @@ class CallNext(Protocol[T, R]):
43
43
  def __call__(self, context: MiddlewareContext[T]) -> Awaitable[R]: ...
44
44
 
45
45
 
46
- ServerResultT = TypeVar(
47
- "ServerResultT",
48
- bound=mt.EmptyResult
49
- | mt.InitializeResult
50
- | mt.CompleteResult
51
- | mt.GetPromptResult
52
- | mt.ListPromptsResult
53
- | mt.ListResourcesResult
54
- | mt.ListResourceTemplatesResult
55
- | mt.ReadResourceResult
56
- | mt.CallToolResult
57
- | mt.ListToolsResult,
58
- )
59
-
60
-
61
- @runtime_checkable
62
- class ServerResultProtocol(Protocol[ServerResultT]):
63
- root: ServerResultT
64
-
65
-
66
46
  @dataclass(kw_only=True, frozen=True)
67
47
  class MiddlewareContext(Generic[T]):
68
48
  """
@@ -167,8 +147,8 @@ class Middleware:
167
147
  async def on_call_tool(
168
148
  self,
169
149
  context: MiddlewareContext[mt.CallToolRequestParams],
170
- call_next: CallNext[mt.CallToolRequestParams, mt.CallToolResult],
171
- ) -> mt.CallToolResult:
150
+ call_next: CallNext[mt.CallToolRequestParams, ToolResult],
151
+ ) -> ToolResult:
172
152
  return await call_next(context)
173
153
 
174
154
  async def on_read_resource(
fastmcp/server/openapi.py CHANGED
@@ -892,7 +892,7 @@ class FastMCPOpenAPI(FastMCP):
892
892
 
893
893
  # Extract output schema from OpenAPI responses
894
894
  output_schema = extract_output_schema_from_responses(
895
- route.responses, route.schema_definitions
895
+ route.responses, route.schema_definitions, route.openapi_version
896
896
  )
897
897
 
898
898
  # Get a unique tool name