fastmcp 2.9.1__py3-none-any.whl → 2.10.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 (42) hide show
  1. fastmcp/cli/cli.py +16 -1
  2. fastmcp/cli/run.py +4 -0
  3. fastmcp/client/auth/oauth.py +5 -82
  4. fastmcp/client/client.py +114 -24
  5. fastmcp/client/elicitation.py +63 -0
  6. fastmcp/client/transports.py +50 -36
  7. fastmcp/contrib/component_manager/README.md +170 -0
  8. fastmcp/contrib/component_manager/__init__.py +4 -0
  9. fastmcp/contrib/component_manager/component_manager.py +186 -0
  10. fastmcp/contrib/component_manager/component_service.py +225 -0
  11. fastmcp/contrib/component_manager/example.py +59 -0
  12. fastmcp/prompts/prompt.py +12 -4
  13. fastmcp/resources/resource.py +8 -3
  14. fastmcp/resources/template.py +5 -0
  15. fastmcp/server/auth/auth.py +15 -0
  16. fastmcp/server/auth/providers/bearer.py +41 -3
  17. fastmcp/server/auth/providers/bearer_env.py +4 -0
  18. fastmcp/server/auth/providers/in_memory.py +15 -0
  19. fastmcp/server/context.py +144 -4
  20. fastmcp/server/elicitation.py +160 -0
  21. fastmcp/server/http.py +1 -9
  22. fastmcp/server/low_level.py +4 -2
  23. fastmcp/server/middleware/__init__.py +14 -1
  24. fastmcp/server/middleware/logging.py +11 -0
  25. fastmcp/server/middleware/middleware.py +10 -6
  26. fastmcp/server/openapi.py +19 -77
  27. fastmcp/server/proxy.py +13 -6
  28. fastmcp/server/server.py +76 -11
  29. fastmcp/settings.py +0 -17
  30. fastmcp/tools/tool.py +209 -57
  31. fastmcp/tools/tool_manager.py +2 -3
  32. fastmcp/tools/tool_transform.py +125 -26
  33. fastmcp/utilities/cli.py +106 -0
  34. fastmcp/utilities/components.py +5 -1
  35. fastmcp/utilities/json_schema_type.py +648 -0
  36. fastmcp/utilities/openapi.py +69 -0
  37. fastmcp/utilities/types.py +50 -19
  38. {fastmcp-2.9.1.dist-info → fastmcp-2.10.0.dist-info}/METADATA +3 -2
  39. {fastmcp-2.9.1.dist-info → fastmcp-2.10.0.dist-info}/RECORD +42 -33
  40. {fastmcp-2.9.1.dist-info → fastmcp-2.10.0.dist-info}/WHEEL +0 -0
  41. {fastmcp-2.9.1.dist-info → fastmcp-2.10.0.dist-info}/entry_points.txt +0 -0
  42. {fastmcp-2.9.1.dist-info → fastmcp-2.10.0.dist-info}/licenses/LICENSE +0 -0
fastmcp/server/server.py CHANGED
@@ -26,6 +26,7 @@ from mcp.server.lowlevel.server import LifespanResultT, NotificationOptions
26
26
  from mcp.server.stdio import stdio_server
27
27
  from mcp.types import (
28
28
  AnyFunction,
29
+ ContentBlock,
29
30
  GetPromptResult,
30
31
  ToolAnnotations,
31
32
  )
@@ -57,12 +58,13 @@ from fastmcp.server.low_level import LowLevelServer
57
58
  from fastmcp.server.middleware import Middleware, MiddlewareContext
58
59
  from fastmcp.settings import Settings
59
60
  from fastmcp.tools import ToolManager
60
- from fastmcp.tools.tool import FunctionTool, Tool
61
+ from fastmcp.tools.tool import FunctionTool, Tool, ToolResult
61
62
  from fastmcp.utilities.cache import TimedCache
63
+ from fastmcp.utilities.cli import print_server_banner
62
64
  from fastmcp.utilities.components import FastMCPComponent
63
65
  from fastmcp.utilities.logging import get_logger
64
66
  from fastmcp.utilities.mcp_config import MCPConfig
65
- from fastmcp.utilities.types import MCPContent
67
+ from fastmcp.utilities.types import NotSet, NotSetT
66
68
 
67
69
  if TYPE_CHECKING:
68
70
  from fastmcp.client import Client
@@ -284,6 +286,7 @@ class FastMCP(Generic[LifespanResultT]):
284
286
  async def run_async(
285
287
  self,
286
288
  transport: Transport | None = None,
289
+ show_banner: bool = True,
287
290
  **transport_kwargs: Any,
288
291
  ) -> None:
289
292
  """Run the FastMCP server asynchronously.
@@ -297,15 +300,23 @@ class FastMCP(Generic[LifespanResultT]):
297
300
  raise ValueError(f"Unknown transport: {transport}")
298
301
 
299
302
  if transport == "stdio":
300
- await self.run_stdio_async(**transport_kwargs)
303
+ await self.run_stdio_async(
304
+ show_banner=show_banner,
305
+ **transport_kwargs,
306
+ )
301
307
  elif transport in {"http", "sse", "streamable-http"}:
302
- await self.run_http_async(transport=transport, **transport_kwargs)
308
+ await self.run_http_async(
309
+ transport=transport,
310
+ show_banner=show_banner,
311
+ **transport_kwargs,
312
+ )
303
313
  else:
304
314
  raise ValueError(f"Unknown transport: {transport}")
305
315
 
306
316
  def run(
307
317
  self,
308
318
  transport: Transport | None = None,
319
+ show_banner: bool = True,
309
320
  **transport_kwargs: Any,
310
321
  ) -> None:
311
322
  """Run the FastMCP server. Note this is a synchronous function.
@@ -314,7 +325,14 @@ class FastMCP(Generic[LifespanResultT]):
314
325
  transport: Transport protocol to use ("stdio", "sse", or "streamable-http")
315
326
  """
316
327
 
317
- anyio.run(partial(self.run_async, transport, **transport_kwargs))
328
+ anyio.run(
329
+ partial(
330
+ self.run_async,
331
+ transport,
332
+ show_banner=show_banner,
333
+ **transport_kwargs,
334
+ )
335
+ )
318
336
 
319
337
  def _setup_handlers(self) -> None:
320
338
  """Set up core MCP protocol handlers."""
@@ -441,7 +459,6 @@ class FastMCP(Generic[LifespanResultT]):
441
459
  """
442
460
  List all available tools, in the format expected by the low-level MCP
443
461
  server.
444
-
445
462
  """
446
463
 
447
464
  async def _handler(
@@ -593,7 +610,7 @@ class FastMCP(Generic[LifespanResultT]):
593
610
 
594
611
  async def _mcp_call_tool(
595
612
  self, key: str, arguments: dict[str, Any]
596
- ) -> list[MCPContent]:
613
+ ) -> list[ContentBlock] | tuple[list[ContentBlock], dict[str, Any]]:
597
614
  """
598
615
  Handle MCP 'callTool' requests.
599
616
 
@@ -610,20 +627,21 @@ class FastMCP(Generic[LifespanResultT]):
610
627
 
611
628
  async with fastmcp.server.context.Context(fastmcp=self):
612
629
  try:
613
- return await self._call_tool(key, arguments)
630
+ result = await self._call_tool(key, arguments)
631
+ return result.to_mcp_result()
614
632
  except DisabledError:
615
633
  raise NotFoundError(f"Unknown tool: {key}")
616
634
  except NotFoundError:
617
635
  raise NotFoundError(f"Unknown tool: {key}")
618
636
 
619
- async def _call_tool(self, key: str, arguments: dict[str, Any]) -> list[MCPContent]:
637
+ async def _call_tool(self, key: str, arguments: dict[str, Any]) -> ToolResult:
620
638
  """
621
639
  Applies this server's middleware and delegates the filtered call to the manager.
622
640
  """
623
641
 
624
642
  async def _handler(
625
643
  context: MiddlewareContext[mcp.types.CallToolRequestParams],
626
- ) -> list[MCPContent]:
644
+ ) -> ToolResult:
627
645
  tool = await self._tool_manager.get_tool(context.message.name)
628
646
  if not self._should_enable_component(tool):
629
647
  raise NotFoundError(f"Unknown tool: {context.message.name!r}")
@@ -789,8 +807,10 @@ class FastMCP(Generic[LifespanResultT]):
789
807
  name_or_fn: AnyFunction,
790
808
  *,
791
809
  name: str | None = None,
810
+ title: str | None = None,
792
811
  description: str | None = None,
793
812
  tags: set[str] | None = None,
813
+ output_schema: dict[str, Any] | None | NotSetT = NotSet,
794
814
  annotations: ToolAnnotations | dict[str, Any] | None = None,
795
815
  exclude_args: list[str] | None = None,
796
816
  enabled: bool | None = None,
@@ -802,8 +822,10 @@ class FastMCP(Generic[LifespanResultT]):
802
822
  name_or_fn: str | None = None,
803
823
  *,
804
824
  name: str | None = None,
825
+ title: str | None = None,
805
826
  description: str | None = None,
806
827
  tags: set[str] | None = None,
828
+ output_schema: dict[str, Any] | None | NotSetT = NotSet,
807
829
  annotations: ToolAnnotations | dict[str, Any] | None = None,
808
830
  exclude_args: list[str] | None = None,
809
831
  enabled: bool | None = None,
@@ -814,8 +836,10 @@ class FastMCP(Generic[LifespanResultT]):
814
836
  name_or_fn: str | AnyFunction | None = None,
815
837
  *,
816
838
  name: str | None = None,
839
+ title: str | None = None,
817
840
  description: str | None = None,
818
841
  tags: set[str] | None = None,
842
+ output_schema: dict[str, Any] | None | NotSetT = NotSet,
819
843
  annotations: ToolAnnotations | dict[str, Any] | None = None,
820
844
  exclude_args: list[str] | None = None,
821
845
  enabled: bool | None = None,
@@ -838,6 +862,7 @@ class FastMCP(Generic[LifespanResultT]):
838
862
  name: Optional name for the tool (keyword-only, alternative to name_or_fn)
839
863
  description: Optional description of what the tool does
840
864
  tags: Optional set of tags for categorizing the tool
865
+ output_schema: Optional JSON schema for the tool's output
841
866
  annotations: Optional annotations about the tool's behavior
842
867
  exclude_args: Optional list of argument names to exclude from the tool schema
843
868
  enabled: Optional boolean to enable or disable the tool
@@ -892,8 +917,10 @@ class FastMCP(Generic[LifespanResultT]):
892
917
  tool = Tool.from_function(
893
918
  fn,
894
919
  name=tool_name,
920
+ title=title,
895
921
  description=description,
896
922
  tags=tags,
923
+ output_schema=output_schema,
897
924
  annotations=annotations,
898
925
  exclude_args=exclude_args,
899
926
  serializer=self._tool_serializer,
@@ -922,8 +949,10 @@ class FastMCP(Generic[LifespanResultT]):
922
949
  return partial(
923
950
  self.tool,
924
951
  name=tool_name,
952
+ title=title,
925
953
  description=description,
926
954
  tags=tags,
955
+ output_schema=output_schema,
927
956
  annotations=annotations,
928
957
  exclude_args=exclude_args,
929
958
  enabled=enabled,
@@ -1009,6 +1038,7 @@ class FastMCP(Generic[LifespanResultT]):
1009
1038
  uri: str,
1010
1039
  *,
1011
1040
  name: str | None = None,
1041
+ title: str | None = None,
1012
1042
  description: str | None = None,
1013
1043
  mime_type: str | None = None,
1014
1044
  tags: set[str] | None = None,
@@ -1100,6 +1130,7 @@ class FastMCP(Generic[LifespanResultT]):
1100
1130
  fn=fn,
1101
1131
  uri_template=uri,
1102
1132
  name=name,
1133
+ title=title,
1103
1134
  description=description,
1104
1135
  mime_type=mime_type,
1105
1136
  tags=tags,
@@ -1112,6 +1143,7 @@ class FastMCP(Generic[LifespanResultT]):
1112
1143
  fn=fn,
1113
1144
  uri=uri,
1114
1145
  name=name,
1146
+ title=title,
1115
1147
  description=description,
1116
1148
  mime_type=mime_type,
1117
1149
  tags=tags,
@@ -1151,6 +1183,7 @@ class FastMCP(Generic[LifespanResultT]):
1151
1183
  name_or_fn: AnyFunction,
1152
1184
  *,
1153
1185
  name: str | None = None,
1186
+ title: str | None = None,
1154
1187
  description: str | None = None,
1155
1188
  tags: set[str] | None = None,
1156
1189
  enabled: bool | None = None,
@@ -1162,6 +1195,7 @@ class FastMCP(Generic[LifespanResultT]):
1162
1195
  name_or_fn: str | None = None,
1163
1196
  *,
1164
1197
  name: str | None = None,
1198
+ title: str | None = None,
1165
1199
  description: str | None = None,
1166
1200
  tags: set[str] | None = None,
1167
1201
  enabled: bool | None = None,
@@ -1172,6 +1206,7 @@ class FastMCP(Generic[LifespanResultT]):
1172
1206
  name_or_fn: str | AnyFunction | None = None,
1173
1207
  *,
1174
1208
  name: str | None = None,
1209
+ title: str | None = None,
1175
1210
  description: str | None = None,
1176
1211
  tags: set[str] | None = None,
1177
1212
  enabled: bool | None = None,
@@ -1268,6 +1303,7 @@ class FastMCP(Generic[LifespanResultT]):
1268
1303
  prompt = Prompt.from_function(
1269
1304
  fn=fn,
1270
1305
  name=prompt_name,
1306
+ title=title,
1271
1307
  description=description,
1272
1308
  tags=tags,
1273
1309
  enabled=enabled,
@@ -1296,13 +1332,22 @@ class FastMCP(Generic[LifespanResultT]):
1296
1332
  return partial(
1297
1333
  self.prompt,
1298
1334
  name=prompt_name,
1335
+ title=title,
1299
1336
  description=description,
1300
1337
  tags=tags,
1301
1338
  enabled=enabled,
1302
1339
  )
1303
1340
 
1304
- async def run_stdio_async(self) -> None:
1341
+ async def run_stdio_async(self, show_banner: bool = True) -> None:
1305
1342
  """Run the server using stdio transport."""
1343
+
1344
+ # Display server banner
1345
+ if show_banner:
1346
+ print_server_banner(
1347
+ server=self,
1348
+ transport="stdio",
1349
+ )
1350
+
1306
1351
  async with stdio_server() as (read_stream, write_stream):
1307
1352
  logger.info(f"Starting MCP server {self.name!r} with transport 'stdio'")
1308
1353
  await self._mcp_server.run(
@@ -1315,6 +1360,7 @@ class FastMCP(Generic[LifespanResultT]):
1315
1360
 
1316
1361
  async def run_http_async(
1317
1362
  self,
1363
+ show_banner: bool = True,
1318
1364
  transport: Literal["http", "streamable-http", "sse"] = "http",
1319
1365
  host: str | None = None,
1320
1366
  port: int | None = None,
@@ -1333,6 +1379,7 @@ class FastMCP(Generic[LifespanResultT]):
1333
1379
  path: Path for the endpoint (defaults to settings.streamable_http_path or settings.sse_path)
1334
1380
  uvicorn_config: Additional configuration for the Uvicorn server
1335
1381
  """
1382
+
1336
1383
  host = host or self._deprecated_settings.host
1337
1384
  port = port or self._deprecated_settings.port
1338
1385
  default_log_level_to_use = (
@@ -1341,6 +1388,23 @@ class FastMCP(Generic[LifespanResultT]):
1341
1388
 
1342
1389
  app = self.http_app(path=path, transport=transport, middleware=middleware)
1343
1390
 
1391
+ # Get the path for the server URL
1392
+ server_path = (
1393
+ app.state.path.lstrip("/")
1394
+ if hasattr(app, "state") and hasattr(app.state, "path")
1395
+ else path or ""
1396
+ )
1397
+
1398
+ # Display server banner
1399
+ if show_banner:
1400
+ print_server_banner(
1401
+ server=self,
1402
+ transport=transport,
1403
+ host=host,
1404
+ port=port,
1405
+ path=server_path,
1406
+ )
1407
+
1344
1408
  _uvicorn_config_from_user = uvicorn_config or {}
1345
1409
 
1346
1410
  config_kwargs: dict[str, Any] = {
@@ -1358,6 +1422,7 @@ class FastMCP(Generic[LifespanResultT]):
1358
1422
  logger.info(
1359
1423
  f"Starting MCP server {self.name!r} with transport {transport!r} on http://{host}:{port}/{path}"
1360
1424
  )
1425
+
1361
1426
  await server.serve()
1362
1427
 
1363
1428
  async def run_sse_async(
fastmcp/settings.py CHANGED
@@ -154,23 +154,6 @@ class Settings(BaseSettings):
154
154
  ),
155
155
  ] = "path"
156
156
 
157
- tool_attempt_parse_json_args: Annotated[
158
- bool,
159
- Field(
160
- default=False,
161
- description=inspect.cleandoc(
162
- """
163
- Note: this enables a legacy behavior. If True, will attempt to parse
164
- stringified JSON lists and objects strings in tool arguments before
165
- passing them to the tool. This is an old behavior that can create
166
- unexpected type coercion issues, but may be helpful for less powerful
167
- LLMs that stringify JSON instead of passing actual lists and objects.
168
- Defaults to False.
169
- """
170
- ),
171
- ),
172
- ] = False
173
-
174
157
  client_init_timeout: Annotated[
175
158
  float | None,
176
159
  Field(