fastmcp 2.10.6__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 (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 +17 -2
  30. fastmcp/server/auth/auth.py +144 -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 +170 -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 +62 -30
  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 +35 -2
  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 +240 -50
  52. fastmcp/utilities/tests.py +54 -6
  53. fastmcp/utilities/types.py +89 -11
  54. {fastmcp-2.10.6.dist-info → fastmcp-2.11.0.dist-info}/METADATA +4 -3
  55. fastmcp-2.11.0.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.0.dist-info}/WHEEL +0 -0
  60. {fastmcp-2.10.6.dist-info → fastmcp-2.11.0.dist-info}/entry_points.txt +0 -0
  61. {fastmcp-2.10.6.dist-info → fastmcp-2.11.0.dist-info}/licenses/LICENSE +0 -0
@@ -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
fastmcp/server/proxy.py CHANGED
@@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Any, cast
7
7
  from urllib.parse import quote
8
8
 
9
9
  import mcp.types
10
+ from mcp import ServerSession
10
11
  from mcp.client.session import ClientSession
11
12
  from mcp.shared.context import LifespanContextT, RequestContext
12
13
  from mcp.shared.exceptions import McpError
@@ -36,6 +37,9 @@ from fastmcp.server.dependencies import get_context
36
37
  from fastmcp.server.server import FastMCP
37
38
  from fastmcp.tools.tool import Tool, ToolResult
38
39
  from fastmcp.tools.tool_manager import ToolManager
40
+ from fastmcp.tools.tool_transform import (
41
+ apply_transformations_to_tools,
42
+ )
39
43
  from fastmcp.utilities.components import MirroredComponent
40
44
  from fastmcp.utilities.logging import get_logger
41
45
 
@@ -71,7 +75,12 @@ class ProxyToolManager(ToolManager):
71
75
  else:
72
76
  raise e
73
77
 
74
- return all_tools
78
+ transformed_tools = apply_transformations_to_tools(
79
+ tools=all_tools,
80
+ transformations=self.transformations,
81
+ )
82
+
83
+ return transformed_tools
75
84
 
76
85
  async def list_tools(self) -> list[Tool]:
77
86
  """Gets the filtered list of tools including local, mounted, and proxy tools."""
@@ -246,6 +255,8 @@ class ProxyTool(Tool, MirroredComponent):
246
255
  parameters=mcp_tool.inputSchema,
247
256
  annotations=mcp_tool.annotations,
248
257
  output_schema=mcp_tool.outputSchema,
258
+ meta=mcp_tool.meta,
259
+ tags=(mcp_tool.meta or {}).get("_fastmcp", {}).get("tags", []),
249
260
  _mirrored=True,
250
261
  )
251
262
 
@@ -294,12 +305,15 @@ class ProxyResource(Resource, MirroredComponent):
294
305
  mcp_resource: mcp.types.Resource,
295
306
  ) -> ProxyResource:
296
307
  """Factory method to create a ProxyResource from a raw MCP resource schema."""
308
+
297
309
  return cls(
298
310
  client=client,
299
311
  uri=mcp_resource.uri,
300
312
  name=mcp_resource.name,
301
313
  description=mcp_resource.description,
302
314
  mime_type=mcp_resource.mimeType or "text/plain",
315
+ meta=mcp_resource.meta,
316
+ tags=(mcp_resource.meta or {}).get("_fastmcp", {}).get("tags", []),
303
317
  _mirrored=True,
304
318
  )
305
319
 
@@ -339,6 +353,8 @@ class ProxyTemplate(ResourceTemplate, MirroredComponent):
339
353
  description=mcp_template.description,
340
354
  mime_type=mcp_template.mimeType or "text/plain",
341
355
  parameters={}, # Remote templates don't have local parameters
356
+ meta=mcp_template.meta,
357
+ tags=(mcp_template.meta or {}).get("_fastmcp", {}).get("tags", []),
342
358
  _mirrored=True,
343
359
  )
344
360
 
@@ -371,6 +387,8 @@ class ProxyTemplate(ResourceTemplate, MirroredComponent):
371
387
  name=self.name,
372
388
  description=self.description,
373
389
  mime_type=result[0].mimeType,
390
+ meta=self.meta,
391
+ tags=(self.meta or {}).get("_fastmcp", {}).get("tags", []),
374
392
  _value=value,
375
393
  )
376
394
 
@@ -404,6 +422,8 @@ class ProxyPrompt(Prompt, MirroredComponent):
404
422
  name=mcp_prompt.name,
405
423
  description=mcp_prompt.description,
406
424
  arguments=arguments,
425
+ meta=mcp_prompt.meta,
426
+ tags=(mcp_prompt.meta or {}).get("_fastmcp", {}).get("tags", []),
407
427
  _mirrored=True,
408
428
  )
409
429
 
@@ -469,7 +489,11 @@ class FastMCPProxy(FastMCP):
469
489
  raise ValueError("Must specify 'client_factory'")
470
490
 
471
491
  # Replace the default managers with our specialized proxy managers.
472
- self._tool_manager = ProxyToolManager(client_factory=self.client_factory)
492
+ self._tool_manager = ProxyToolManager(
493
+ client_factory=self.client_factory,
494
+ # Propagate the transformations from the base class tool manager
495
+ transformations=self._tool_manager.transformations,
496
+ )
473
497
  self._resource_manager = ProxyResourceManager(
474
498
  client_factory=self.client_factory
475
499
  )
@@ -554,11 +578,12 @@ class ProxyClient(Client[ClientTransportT]):
554
578
  A handler that forwards the elicitation request from the remote server to the proxy's connected clients and relays the response back to the remote server.
555
579
  """
556
580
  ctx = get_context()
557
- result = await ctx.elicit(message, response_type)
558
- if result.action == "accept":
559
- return result.data
560
- else:
561
- return ElicitResult(action=result.action)
581
+ result = await ctx.session.elicit(
582
+ message=message,
583
+ requestedSchema=params.requestedSchema,
584
+ related_request_id=ctx.request_id,
585
+ )
586
+ return ElicitResult(action=result.action, content=result.content)
562
587
 
563
588
  @classmethod
564
589
  async def default_log_handler(cls, message: LogMessage) -> None:
@@ -566,7 +591,9 @@ class ProxyClient(Client[ClientTransportT]):
566
591
  A handler that forwards the log notification from the remote server to the proxy's connected clients.
567
592
  """
568
593
  ctx = get_context()
569
- await ctx.log(message.data, level=message.level, logger_name=message.logger)
594
+ msg = message.data.get("msg")
595
+ extra = message.data.get("extra")
596
+ await ctx.log(msg, level=message.level, logger_name=message.logger, extra=extra)
570
597
 
571
598
  @classmethod
572
599
  async def default_progress_handler(
@@ -593,6 +620,10 @@ class StatefulProxyClient(ProxyClient[ClientTransportT]):
593
620
  Note that it is essential to ensure that the proxy server itself is also stateful.
594
621
  """
595
622
 
623
+ def __init__(self, *args, **kwargs):
624
+ super().__init__(*args, **kwargs)
625
+ self._caches: dict[ServerSession, Client[ClientTransportT]] = {}
626
+
596
627
  async def __aexit__(self, exc_type, exc_value, traceback) -> None:
597
628
  """
598
629
  The stateful proxy client will be forced disconnected when the session is exited.
@@ -600,6 +631,14 @@ class StatefulProxyClient(ProxyClient[ClientTransportT]):
600
631
  """
601
632
  pass
602
633
 
634
+ async def clear(self):
635
+ """
636
+ Clear all cached clients and force disconnect them.
637
+ """
638
+ while self._caches:
639
+ _, cache = self._caches.popitem()
640
+ await cache._disconnect(force=True)
641
+
603
642
  def new_stateful(self) -> Client[ClientTransportT]:
604
643
  """
605
644
  Create a new stateful proxy client instance with the same configuration.
@@ -607,15 +646,15 @@ class StatefulProxyClient(ProxyClient[ClientTransportT]):
607
646
  Use this method as the client factory for stateful proxy server.
608
647
  """
609
648
  session = get_context().session
610
- proxy_client = session.__dict__.get("_proxy_client", None)
649
+ proxy_client = self._caches.get(session, None)
611
650
 
612
651
  if proxy_client is None:
613
652
  proxy_client = self.new()
614
653
  logger.debug(f"{proxy_client} created for {session}")
615
- session.__dict__["_proxy_client"] = proxy_client
654
+ self._caches[session] = proxy_client
616
655
 
617
656
  async def _on_session_exit():
618
- proxy_client: Client = session.__dict__.pop("_proxy_client")
657
+ self._caches.pop(session)
619
658
  logger.debug(f"{proxy_client} will be disconnect")
620
659
  await proxy_client._disconnect(force=True)
621
660