fastmcp 2.10.1__py3-none-any.whl → 2.10.2__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.
fastmcp/cli/cli.py CHANGED
@@ -64,6 +64,7 @@ def _build_uv_command(
64
64
  server_spec: str,
65
65
  with_editable: Path | None = None,
66
66
  with_packages: list[str] | None = None,
67
+ no_banner: bool = False,
67
68
  ) -> list[str]:
68
69
  """Build the uv run command that runs a MCP server through mcp run."""
69
70
  cmd = ["uv"]
@@ -80,6 +81,10 @@ def _build_uv_command(
80
81
 
81
82
  # Add mcp run command
82
83
  cmd.extend(["fastmcp", "run", server_spec])
84
+
85
+ if no_banner:
86
+ cmd.append("--no-banner")
87
+
83
88
  return cmd
84
89
 
85
90
 
@@ -192,7 +197,9 @@ def dev(
192
197
  if inspector_version:
193
198
  inspector_cmd += f"@{inspector_version}"
194
199
 
195
- uv_cmd = _build_uv_command(server_spec, with_editable, with_packages)
200
+ uv_cmd = _build_uv_command(
201
+ server_spec, with_editable, with_packages, no_banner=True
202
+ )
196
203
 
197
204
  # Run the MCP Inspector command with shell=True on Windows
198
205
  shell = sys.platform == "win32"
@@ -261,6 +268,13 @@ def run(
261
268
  help="Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)",
262
269
  ),
263
270
  ] = None,
271
+ no_banner: Annotated[
272
+ bool,
273
+ typer.Option(
274
+ "--no-banner",
275
+ help="Don't show the server banner",
276
+ ),
277
+ ] = False,
264
278
  ) -> None:
265
279
  """Run a MCP server or connect to a remote one.
266
280
 
@@ -297,6 +311,7 @@ def run(
297
311
  port=port,
298
312
  log_level=log_level,
299
313
  server_args=server_args,
314
+ show_banner=not no_banner,
300
315
  )
301
316
  except Exception as e:
302
317
  logger.error(
fastmcp/cli/run.py CHANGED
@@ -169,6 +169,7 @@ def run_command(
169
169
  port: int | None = None,
170
170
  log_level: str | None = None,
171
171
  server_args: list[str] | None = None,
172
+ show_banner: bool = True,
172
173
  ) -> None:
173
174
  """Run a MCP server or connect to a remote one.
174
175
 
@@ -201,6 +202,9 @@ def run_command(
201
202
  if log_level:
202
203
  kwargs["log_level"] = log_level
203
204
 
205
+ if not show_banner:
206
+ kwargs["show_banner"] = False
207
+
204
208
  try:
205
209
  server.run(**kwargs)
206
210
  except Exception as e:
@@ -53,6 +53,11 @@ def create_elicitation_callback(
53
53
  if not isinstance(result, ElicitResult):
54
54
  result = ElicitResult(action="accept", content=result)
55
55
  content = to_jsonable_python(result.content)
56
+ if not isinstance(content, dict | None):
57
+ raise ValueError(
58
+ "Elicitation responses must be serializable as a JSON object (dict). Received: "
59
+ f"{result.content!r}"
60
+ )
56
61
  return MCPElicitResult(**result.model_dump() | {"content": content})
57
62
  except Exception as e:
58
63
  return mcp.types.ErrorData(
@@ -773,8 +773,6 @@ class MCPConfigTransport(ClientTransport):
773
773
  """
774
774
 
775
775
  def __init__(self, config: MCPConfig | dict):
776
- from fastmcp.client.client import Client
777
-
778
776
  if isinstance(config, dict):
779
777
  config = MCPConfig.from_dict(config)
780
778
  self.config = config
@@ -792,9 +790,9 @@ class MCPConfigTransport(ClientTransport):
792
790
  composite_server = FastMCP()
793
791
 
794
792
  for name, server in self.config.mcpServers.items():
795
- server_client = Client(transport=server.to_transport())
796
793
  composite_server.mount(
797
- prefix=name, server=FastMCP.as_proxy(server_client)
794
+ prefix=name,
795
+ server=FastMCP.as_proxy(backend=server.to_transport()),
798
796
  )
799
797
 
800
798
  self.transport = FastMCPTransport(mcp=composite_server)
@@ -399,12 +399,21 @@ class BearerAuthProvider(OAuthProvider):
399
399
  return None
400
400
 
401
401
  def _extract_scopes(self, claims: dict[str, Any]) -> list[str]:
402
- """Extract scopes from JWT claims."""
403
- scope_claim = claims.get("scope", "")
404
- if isinstance(scope_claim, str):
405
- return scope_claim.split()
406
- elif isinstance(scope_claim, list):
407
- return scope_claim
402
+ """
403
+ Extract scopes from JWT claims. Supports both 'scope' and 'scp'
404
+ claims.
405
+
406
+ Checks the `scope` claim first (standard OAuth2 claim), then the `scp`
407
+ claim (used by some Identity Providers).
408
+ """
409
+
410
+ for claim in ["scope", "scp"]:
411
+ if claim in claims:
412
+ if isinstance(claims[claim], str):
413
+ return claims[claim].split()
414
+ elif isinstance(claims[claim], list):
415
+ return claims[claim]
416
+
408
417
  return []
409
418
 
410
419
  async def verify_token(self, token: str) -> AccessToken | None:
fastmcp/server/proxy.py CHANGED
@@ -1,9 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from pathlib import Path
3
4
  from typing import TYPE_CHECKING, Any, cast
4
5
  from urllib.parse import quote
5
6
 
6
7
  import mcp.types
8
+ from mcp.client.session import ClientSession
9
+ from mcp.shared.context import LifespanContextT, RequestContext
7
10
  from mcp.shared.exceptions import McpError
8
11
  from mcp.types import (
9
12
  METHOD_NOT_FOUND,
@@ -14,6 +17,10 @@ from mcp.types import (
14
17
  from pydantic.networks import AnyUrl
15
18
 
16
19
  from fastmcp.client import Client
20
+ from fastmcp.client.elicitation import ElicitResult
21
+ from fastmcp.client.logging import LogMessage
22
+ from fastmcp.client.roots import RootsList
23
+ from fastmcp.client.transports import ClientTransportT
17
24
  from fastmcp.exceptions import NotFoundError, ResourceError, ToolError
18
25
  from fastmcp.prompts import Prompt, PromptMessage
19
26
  from fastmcp.prompts.prompt import PromptArgument
@@ -21,10 +28,12 @@ from fastmcp.prompts.prompt_manager import PromptManager
21
28
  from fastmcp.resources import Resource, ResourceTemplate
22
29
  from fastmcp.resources.resource_manager import ResourceManager
23
30
  from fastmcp.server.context import Context
31
+ from fastmcp.server.dependencies import get_context
24
32
  from fastmcp.server.server import FastMCP
25
33
  from fastmcp.tools.tool import Tool, ToolResult
26
34
  from fastmcp.tools.tool_manager import ToolManager
27
35
  from fastmcp.utilities.logging import get_logger
36
+ from fastmcp.utilities.mcp_config import MCPConfig
28
37
 
29
38
  if TYPE_CHECKING:
30
39
  from fastmcp.server import Context
@@ -406,3 +415,110 @@ class FastMCPProxy(FastMCP):
406
415
  self._tool_manager = ProxyToolManager(client=self.client)
407
416
  self._resource_manager = ProxyResourceManager(client=self.client)
408
417
  self._prompt_manager = ProxyPromptManager(client=self.client)
418
+
419
+
420
+ async def default_proxy_roots_handler(
421
+ context: RequestContext[ClientSession, LifespanContextT],
422
+ ) -> RootsList:
423
+ """
424
+ A handler that forwards the list roots request from the remote server to the proxy's connected clients and relays the response back to the remote server.
425
+ """
426
+ ctx = get_context()
427
+ return await ctx.list_roots()
428
+
429
+
430
+ class ProxyClient(Client[ClientTransportT]):
431
+ """
432
+ A proxy client that forwards advanced interactions between a remote MCP server and the proxy's connected clients.
433
+ Supports forwarding roots, sampling, elicitation, logging, and progress.
434
+ """
435
+
436
+ def __init__(
437
+ self,
438
+ transport: (
439
+ ClientTransportT
440
+ | FastMCP
441
+ | AnyUrl
442
+ | Path
443
+ | MCPConfig
444
+ | dict[str, Any]
445
+ | str
446
+ ),
447
+ **kwargs,
448
+ ):
449
+ if "roots" not in kwargs:
450
+ kwargs["roots"] = default_proxy_roots_handler
451
+ if "sampling_handler" not in kwargs:
452
+ kwargs["sampling_handler"] = ProxyClient.default_sampling_handler
453
+ if "elicitation_handler" not in kwargs:
454
+ kwargs["elicitation_handler"] = ProxyClient.default_elicitation_handler
455
+ if "log_handler" not in kwargs:
456
+ kwargs["log_handler"] = ProxyClient.default_log_handler
457
+ if "progress_handler" not in kwargs:
458
+ kwargs["progress_handler"] = ProxyClient.default_progress_handler
459
+ super().__init__(transport, **kwargs)
460
+
461
+ @classmethod
462
+ async def default_sampling_handler(
463
+ cls,
464
+ messages: list[mcp.types.SamplingMessage],
465
+ params: mcp.types.CreateMessageRequestParams,
466
+ context: RequestContext[ClientSession, LifespanContextT],
467
+ ) -> mcp.types.CreateMessageResult:
468
+ """
469
+ A handler that forwards the sampling request from the remote server to the proxy's connected clients and relays the response back to the remote server.
470
+ """
471
+ ctx = get_context()
472
+ content = await ctx.sample(
473
+ [msg for msg in messages],
474
+ system_prompt=params.systemPrompt,
475
+ temperature=params.temperature,
476
+ max_tokens=params.maxTokens,
477
+ model_preferences=params.modelPreferences,
478
+ )
479
+ if isinstance(content, mcp.types.ResourceLink | mcp.types.EmbeddedResource):
480
+ raise RuntimeError("Content is not supported")
481
+ return mcp.types.CreateMessageResult(
482
+ role="assistant",
483
+ model="fastmcp-client",
484
+ content=content,
485
+ )
486
+
487
+ @classmethod
488
+ async def default_elicitation_handler(
489
+ cls,
490
+ message: str,
491
+ response_type: type,
492
+ params: mcp.types.ElicitRequestParams,
493
+ context: RequestContext[ClientSession, LifespanContextT],
494
+ ) -> ElicitResult:
495
+ """
496
+ 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.
497
+ """
498
+ ctx = get_context()
499
+ result = await ctx.elicit(message, response_type)
500
+ if result.action == "accept":
501
+ return result.data
502
+ else:
503
+ return ElicitResult(action=result.action)
504
+
505
+ @classmethod
506
+ async def default_log_handler(cls, message: LogMessage) -> None:
507
+ """
508
+ A handler that forwards the log notification from the remote server to the proxy's connected clients.
509
+ """
510
+ ctx = get_context()
511
+ await ctx.log(message.data, level=message.level, logger_name=message.logger)
512
+
513
+ @classmethod
514
+ async def default_progress_handler(
515
+ cls,
516
+ progress: float,
517
+ total: float | None,
518
+ message: str | None,
519
+ ) -> None:
520
+ """
521
+ A handler that forwards the progress notification from the remote server to the proxy's connected clients.
522
+ """
523
+ ctx = get_context()
524
+ await ctx.report_progress(progress, total, message)
fastmcp/server/server.py CHANGED
@@ -60,6 +60,7 @@ from fastmcp.settings import Settings
60
60
  from fastmcp.tools import ToolManager
61
61
  from fastmcp.tools.tool import FunctionTool, Tool, ToolResult
62
62
  from fastmcp.utilities.cache import TimedCache
63
+ from fastmcp.utilities.cli import log_server_banner
63
64
  from fastmcp.utilities.components import FastMCPComponent
64
65
  from fastmcp.utilities.logging import get_logger
65
66
  from fastmcp.utilities.mcp_config import MCPConfig
@@ -285,6 +286,7 @@ class FastMCP(Generic[LifespanResultT]):
285
286
  async def run_async(
286
287
  self,
287
288
  transport: Transport | None = None,
289
+ show_banner: bool = True,
288
290
  **transport_kwargs: Any,
289
291
  ) -> None:
290
292
  """Run the FastMCP server asynchronously.
@@ -298,15 +300,23 @@ class FastMCP(Generic[LifespanResultT]):
298
300
  raise ValueError(f"Unknown transport: {transport}")
299
301
 
300
302
  if transport == "stdio":
301
- await self.run_stdio_async(**transport_kwargs)
303
+ await self.run_stdio_async(
304
+ show_banner=show_banner,
305
+ **transport_kwargs,
306
+ )
302
307
  elif transport in {"http", "sse", "streamable-http"}:
303
- 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
+ )
304
313
  else:
305
314
  raise ValueError(f"Unknown transport: {transport}")
306
315
 
307
316
  def run(
308
317
  self,
309
318
  transport: Transport | None = None,
319
+ show_banner: bool = True,
310
320
  **transport_kwargs: Any,
311
321
  ) -> None:
312
322
  """Run the FastMCP server. Note this is a synchronous function.
@@ -315,7 +325,14 @@ class FastMCP(Generic[LifespanResultT]):
315
325
  transport: Transport protocol to use ("stdio", "sse", or "streamable-http")
316
326
  """
317
327
 
318
- 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
+ )
319
336
 
320
337
  def _setup_handlers(self) -> None:
321
338
  """Set up core MCP protocol handlers."""
@@ -1321,8 +1338,16 @@ class FastMCP(Generic[LifespanResultT]):
1321
1338
  enabled=enabled,
1322
1339
  )
1323
1340
 
1324
- async def run_stdio_async(self) -> None:
1341
+ async def run_stdio_async(self, show_banner: bool = True) -> None:
1325
1342
  """Run the server using stdio transport."""
1343
+
1344
+ # Display server banner
1345
+ if show_banner:
1346
+ log_server_banner(
1347
+ server=self,
1348
+ transport="stdio",
1349
+ )
1350
+
1326
1351
  async with stdio_server() as (read_stream, write_stream):
1327
1352
  logger.info(f"Starting MCP server {self.name!r} with transport 'stdio'")
1328
1353
  await self._mcp_server.run(
@@ -1335,6 +1360,7 @@ class FastMCP(Generic[LifespanResultT]):
1335
1360
 
1336
1361
  async def run_http_async(
1337
1362
  self,
1363
+ show_banner: bool = True,
1338
1364
  transport: Literal["http", "streamable-http", "sse"] = "http",
1339
1365
  host: str | None = None,
1340
1366
  port: int | None = None,
@@ -1342,6 +1368,7 @@ class FastMCP(Generic[LifespanResultT]):
1342
1368
  path: str | None = None,
1343
1369
  uvicorn_config: dict[str, Any] | None = None,
1344
1370
  middleware: list[ASGIMiddleware] | None = None,
1371
+ stateless_http: bool | None = None,
1345
1372
  ) -> None:
1346
1373
  """Run the server using HTTP transport.
1347
1374
 
@@ -1352,15 +1379,39 @@ class FastMCP(Generic[LifespanResultT]):
1352
1379
  log_level: Log level for the server (defaults to settings.log_level)
1353
1380
  path: Path for the endpoint (defaults to settings.streamable_http_path or settings.sse_path)
1354
1381
  uvicorn_config: Additional configuration for the Uvicorn server
1382
+ middleware: A list of middleware to apply to the app
1383
+ stateless_http: Whether to use stateless HTTP (defaults to settings.stateless_http)
1355
1384
  """
1385
+
1356
1386
  host = host or self._deprecated_settings.host
1357
1387
  port = port or self._deprecated_settings.port
1358
1388
  default_log_level_to_use = (
1359
1389
  log_level or self._deprecated_settings.log_level
1360
1390
  ).lower()
1361
1391
 
1362
- app = self.http_app(path=path, transport=transport, middleware=middleware)
1392
+ app = self.http_app(
1393
+ path=path,
1394
+ transport=transport,
1395
+ middleware=middleware,
1396
+ stateless_http=stateless_http,
1397
+ )
1398
+
1399
+ # Get the path for the server URL
1400
+ server_path = (
1401
+ app.state.path.lstrip("/")
1402
+ if hasattr(app, "state") and hasattr(app.state, "path")
1403
+ else path or ""
1404
+ )
1363
1405
 
1406
+ # Display server banner
1407
+ if show_banner:
1408
+ log_server_banner(
1409
+ server=self,
1410
+ transport=transport,
1411
+ host=host,
1412
+ port=port,
1413
+ path=server_path,
1414
+ )
1364
1415
  _uvicorn_config_from_user = uvicorn_config or {}
1365
1416
 
1366
1417
  config_kwargs: dict[str, Any] = {
@@ -1378,6 +1429,7 @@ class FastMCP(Generic[LifespanResultT]):
1378
1429
  logger.info(
1379
1430
  f"Starting MCP server {self.name!r} with transport {transport!r} on http://{host}:{port}/{path}"
1380
1431
  )
1432
+
1381
1433
  await server.serve()
1382
1434
 
1383
1435
  async def run_sse_async(
@@ -1591,9 +1643,8 @@ class FastMCP(Generic[LifespanResultT]):
1591
1643
  resource_separator: Deprecated. Separator character for resource URIs.
1592
1644
  prompt_separator: Deprecated. Separator character for prompt names.
1593
1645
  """
1594
- from fastmcp import Client
1595
1646
  from fastmcp.client.transports import FastMCPTransport
1596
- from fastmcp.server.proxy import FastMCPProxy
1647
+ from fastmcp.server.proxy import FastMCPProxy, ProxyClient
1597
1648
 
1598
1649
  # Deprecated since 2.9.0
1599
1650
  # Prior to 2.9.0, the first positional argument was the prefix and the
@@ -1645,7 +1696,7 @@ class FastMCP(Generic[LifespanResultT]):
1645
1696
  as_proxy = server._has_lifespan
1646
1697
 
1647
1698
  if as_proxy and not isinstance(server, FastMCPProxy):
1648
- server = FastMCPProxy(Client(transport=FastMCPTransport(server)))
1699
+ server = FastMCPProxy(ProxyClient(transport=FastMCPTransport(server)))
1649
1700
 
1650
1701
  # Delegate mounting to all three managers
1651
1702
  mounted_server = MountedServer(
@@ -1856,14 +1907,16 @@ class FastMCP(Generic[LifespanResultT]):
1856
1907
  @classmethod
1857
1908
  def as_proxy(
1858
1909
  cls,
1859
- backend: Client[ClientTransportT]
1860
- | ClientTransport
1861
- | FastMCP[Any]
1862
- | AnyUrl
1863
- | Path
1864
- | MCPConfig
1865
- | dict[str, Any]
1866
- | str,
1910
+ backend: (
1911
+ Client[ClientTransportT]
1912
+ | ClientTransport
1913
+ | FastMCP[Any]
1914
+ | AnyUrl
1915
+ | Path
1916
+ | MCPConfig
1917
+ | dict[str, Any]
1918
+ | str
1919
+ ),
1867
1920
  **settings: Any,
1868
1921
  ) -> FastMCPProxy:
1869
1922
  """Create a FastMCP proxy server for the given backend.
@@ -1874,12 +1927,12 @@ class FastMCP(Generic[LifespanResultT]):
1874
1927
  `fastmcp.client.Client` constructor.
1875
1928
  """
1876
1929
  from fastmcp.client.client import Client
1877
- from fastmcp.server.proxy import FastMCPProxy
1930
+ from fastmcp.server.proxy import FastMCPProxy, ProxyClient
1878
1931
 
1879
1932
  if isinstance(backend, Client):
1880
1933
  client = backend
1881
1934
  else:
1882
- client = Client(backend)
1935
+ client = ProxyClient(backend)
1883
1936
 
1884
1937
  return FastMCPProxy(client=client, **settings)
1885
1938
 
fastmcp/settings.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations as _annotations
2
2
 
3
3
  import inspect
4
+ import warnings
4
5
  from pathlib import Path
5
6
  from typing import Annotated, Any, Literal
6
7
 
@@ -258,4 +259,21 @@ class Settings(BaseSettings):
258
259
  ] = None
259
260
 
260
261
 
261
- settings = Settings()
262
+ def __getattr__(name: str):
263
+ """
264
+ Used to deprecate the module-level Image class; can be removed once it is no longer imported to root.
265
+ """
266
+ if name == "settings":
267
+ import fastmcp
268
+
269
+ settings = fastmcp.settings
270
+ # Deprecated in 2.10.2
271
+ if settings.deprecation_warnings:
272
+ warnings.warn(
273
+ "`from fastmcp.settings import settings` is deprecated. use `fasmtpc.settings` instead.",
274
+ DeprecationWarning,
275
+ stacklevel=2,
276
+ )
277
+ return settings
278
+
279
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
@@ -0,0 +1,102 @@
1
+ from __future__ import annotations
2
+
3
+ from importlib.metadata import version
4
+ from typing import TYPE_CHECKING, Any, Literal
5
+
6
+ from rich.console import Console, Group
7
+ from rich.panel import Panel
8
+ from rich.table import Table
9
+ from rich.text import Text
10
+
11
+ import fastmcp
12
+
13
+ if TYPE_CHECKING:
14
+ from fastmcp import FastMCP
15
+
16
+ LOGO_ASCII = r"""
17
+ _ __ ___ ______ __ __ _____________ ____ ____
18
+ _ __ ___ / ____/___ ______/ /_/ |/ / ____/ __ \ |___ \ / __ \
19
+ _ __ ___ / /_ / __ `/ ___/ __/ /|_/ / / / /_/ / ___/ / / / / /
20
+ _ __ ___ / __/ / /_/ (__ ) /_/ / / / /___/ ____/ / __/_/ /_/ /
21
+ _ __ ___ /_/ \__,_/____/\__/_/ /_/\____/_/ /_____(_)____/
22
+
23
+ """.lstrip("\n")
24
+
25
+
26
+ def log_server_banner(
27
+ server: FastMCP[Any],
28
+ transport: Literal["stdio", "http", "sse", "streamable-http"],
29
+ *,
30
+ host: str | None = None,
31
+ port: int | None = None,
32
+ path: str | None = None,
33
+ ) -> None:
34
+ """Creates and logs a formatted banner with server information and logo.
35
+
36
+ Args:
37
+ transport: The transport protocol being used
38
+ server_name: Optional server name to display
39
+ host: Host address (for HTTP transports)
40
+ port: Port number (for HTTP transports)
41
+ path: Server path (for HTTP transports)
42
+ """
43
+
44
+ # Create the logo text
45
+ logo_text = Text(LOGO_ASCII, style="bold green")
46
+
47
+ # Create the information table
48
+ info_table = Table.grid(padding=(0, 1))
49
+ info_table.add_column(style="bold", justify="center") # Emoji column
50
+ info_table.add_column(style="bold cyan", justify="left") # Label column
51
+ info_table.add_column(style="white", justify="left") # Value column
52
+
53
+ match transport:
54
+ case "http" | "streamable-http":
55
+ display_transport = "Streamable-HTTP"
56
+ case "sse":
57
+ display_transport = "SSE"
58
+ case "stdio":
59
+ display_transport = "STDIO"
60
+
61
+ info_table.add_row("🖥️", "Server name:", server.name)
62
+ info_table.add_row("📦", "Transport:", display_transport)
63
+
64
+ # Show connection info based on transport
65
+ if transport in ("http", "streamable-http", "sse"):
66
+ if host and port:
67
+ server_url = f"http://{host}:{port}"
68
+ if path:
69
+ server_url += f"/{path.lstrip('/')}"
70
+ info_table.add_row("🔗", "Server URL:", server_url)
71
+
72
+ # Add documentation link
73
+ info_table.add_row("", "", "")
74
+ info_table.add_row("📚", "Docs:", "https://gofastmcp.com")
75
+ info_table.add_row("🚀", "Deploy:", "https://fastmcp.cloud")
76
+
77
+ # Add version information with explicit style overrides
78
+ info_table.add_row("", "", "")
79
+ info_table.add_row(
80
+ "🏎️",
81
+ "FastMCP version:",
82
+ Text(fastmcp.__version__, style="dim white", no_wrap=True),
83
+ )
84
+ info_table.add_row(
85
+ "🤝",
86
+ "MCP version:",
87
+ Text(version("mcp"), style="dim white", no_wrap=True),
88
+ )
89
+ # Create panel with logo and information using Group
90
+ panel_content = Group(logo_text, "", info_table)
91
+
92
+ panel = Panel(
93
+ panel_content,
94
+ title="FastMCP 2.0",
95
+ title_align="left",
96
+ border_style="dim",
97
+ padding=(2, 5),
98
+ expand=False,
99
+ )
100
+
101
+ console = Console(stderr=True)
102
+ console.print(Group("\n", panel, "\n"))
@@ -50,3 +50,6 @@ def configure_logging(
50
50
  logger.removeHandler(hdlr)
51
51
 
52
52
  logger.addHandler(handler)
53
+
54
+ # Don't propagate to the root logger
55
+ logger.propagate = False
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import datetime
3
4
  import re
4
5
  from typing import TYPE_CHECKING, Annotated, Any, Literal
5
6
  from urllib.parse import urlparse
@@ -65,6 +66,7 @@ class RemoteMCPServer(FastMCPBaseModel):
65
66
  description='Either a string representing a Bearer token, the literal "oauth" to use OAuth authentication, or an httpx.Auth instance for custom authentication.',
66
67
  ),
67
68
  ] = None
69
+ sse_read_timeout: datetime.timedelta | int | float | None = None
68
70
 
69
71
  model_config = ConfigDict(arbitrary_types_allowed=True)
70
72
 
@@ -77,11 +79,19 @@ class RemoteMCPServer(FastMCPBaseModel):
77
79
  transport = self.transport
78
80
 
79
81
  if transport == "sse":
80
- return SSETransport(self.url, headers=self.headers, auth=self.auth)
82
+ return SSETransport(
83
+ self.url,
84
+ headers=self.headers,
85
+ auth=self.auth,
86
+ sse_read_timeout=self.sse_read_timeout,
87
+ )
81
88
  else:
82
89
  # Both "http" and "streamable-http" map to StreamableHttpTransport
83
90
  return StreamableHttpTransport(
84
- self.url, headers=self.headers, auth=self.auth
91
+ self.url,
92
+ headers=self.headers,
93
+ auth=self.auth,
94
+ sse_read_timeout=self.sse_read_timeout,
85
95
  )
86
96
 
87
97
 
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import copy
4
+ import logging
4
5
  import multiprocessing
5
6
  import socket
6
7
  import time
@@ -129,3 +130,15 @@ def run_server_in_process(
129
130
  proc.join(timeout=2)
130
131
  if proc.is_alive():
131
132
  raise RuntimeError("Server process failed to terminate even after kill")
133
+
134
+
135
+ @contextmanager
136
+ def caplog_for_fastmcp(caplog):
137
+ """Context manager to capture logs from FastMCP loggers even when propagation is disabled."""
138
+ caplog.clear()
139
+ logger = logging.getLogger("FastMCP")
140
+ logger.addHandler(caplog.handler)
141
+ try:
142
+ yield
143
+ finally:
144
+ logger.removeHandler(caplog.handler)
@@ -3,6 +3,7 @@
3
3
  import base64
4
4
  import inspect
5
5
  import mimetypes
6
+ import os
6
7
  from collections.abc import Callable
7
8
  from functools import lru_cache
8
9
  from pathlib import Path
@@ -101,7 +102,7 @@ class Image:
101
102
  if path is not None and data is not None:
102
103
  raise ValueError("Only one of path or data can be provided")
103
104
 
104
- self.path = Path(path) if path else None
105
+ self.path = Path(os.path.expandvars(str(path))).expanduser() if path else None
105
106
  self.data = data
106
107
  self._format = format
107
108
  self._mime_type = self._get_mime_type()
@@ -160,7 +161,7 @@ class Audio:
160
161
  if path is not None and data is not None:
161
162
  raise ValueError("Only one of path or data can be provided")
162
163
 
163
- self.path = Path(path) if path else None
164
+ self.path = Path(os.path.expandvars(str(path))).expanduser() if path else None
164
165
  self.data = data
165
166
  self._format = format
166
167
  self._mime_type = self._get_mime_type()
@@ -219,7 +220,7 @@ class File:
219
220
  if path is not None and data is not None:
220
221
  raise ValueError("Only one of path or data can be provided")
221
222
 
222
- self.path = Path(path) if path else None
223
+ self.path = Path(os.path.expandvars(str(path))).expanduser() if path else None
223
224
  self.data = data
224
225
  self._format = format
225
226
  self._mime_type = self._get_mime_type()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastmcp
3
- Version: 2.10.1
3
+ Version: 2.10.2
4
4
  Summary: The fast, Pythonic way to build MCP servers.
5
5
  Project-URL: Homepage, https://gofastmcp.com
6
6
  Project-URL: Repository, https://github.com/jlowin/fastmcp
@@ -1,21 +1,21 @@
1
1
  fastmcp/__init__.py,sha256=5ChT4kg3srdFl0-9dZekGqpzCESlpc6ohrfPbWf1aTo,1300
2
2
  fastmcp/exceptions.py,sha256=-krEavxwddQau6T7MESCR4VjKNLfP9KHJrU1p3y72FU,744
3
3
  fastmcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- fastmcp/settings.py,sha256=yC3f0ITEWYeit37-wnF0G12klTw_hjrWjCr6oZtUT8o,8329
4
+ fastmcp/settings.py,sha256=Ta0TKA75xda9sNkIOpPVIEEk4W9jf_2gwcmO26uDQpg,8946
5
5
  fastmcp/cli/__init__.py,sha256=Ii284TNoG5lxTP40ETMGhHEq3lQZWxu9m9JuU57kUpQ,87
6
6
  fastmcp/cli/claude.py,sha256=IAlcZ4qZKBBj09jZUMEx7EANZE_IR3vcu7zOBJmMOuU,4567
7
- fastmcp/cli/cli.py,sha256=598s1S-KdqIB85mwqvXJaBVen6xYY65ek4v-q3lmY4k,15933
8
- fastmcp/cli/run.py,sha256=Pw3vH5wKRHfbmHRn0saIbC4l450KPOzeQbzPFqG4AbY,6208
7
+ fastmcp/cli/cli.py,sha256=uJqUvFuyN7uslaCe5FHKp-EKdIRY5mRr-81f0c1r4PA,16257
8
+ fastmcp/cli/run.py,sha256=JKBcy935Jy59WInwEaKt1gZkRDIESB7CRw_Oj987PVw,6301
9
9
  fastmcp/client/__init__.py,sha256=kd2hhSuD8rZuF87c9zlPJP_icJ-Rx3exyNoK0EzfOtE,617
10
10
  fastmcp/client/client.py,sha256=8Sx7NxnnF5IMv9E-J0MXjTI_TfhlM3j0mD1Rh-LXQJI,29213
11
- fastmcp/client/elicitation.py,sha256=8eWZpXbRFctZGgulyJ_GGI4GYXqAFf5Zp47vYTRD4fc,2155
11
+ fastmcp/client/elicitation.py,sha256=Jf9yqna8R7r1hqedXAyh9a2-QNVzbCSKUDZhkFHqHqg,2403
12
12
  fastmcp/client/logging.py,sha256=7GJ-BLFW16_IOJPlGTNEWPP0P-yqqRpmsLdiKrlVsw8,757
13
13
  fastmcp/client/messages.py,sha256=NIPjt-5js_DkI5BD4OVdTf6pz-nGjc2dtbgt-vAY234,4329
14
14
  fastmcp/client/oauth_callback.py,sha256=ODAnVX-ettL82RuI5KpfkKf8iDtYMDue3Tnab5sjQtM,10071
15
15
  fastmcp/client/progress.py,sha256=WjLLDbUKMsx8DK-fqO7AGsXb83ak-6BMrLvzzznGmcI,1043
16
16
  fastmcp/client/roots.py,sha256=IxI_bHwHTmg6c2H-s1av1ZgrRnNDieHtYwdGFbzXT5c,2471
17
17
  fastmcp/client/sampling.py,sha256=Q8PzYCERa1W3xGGI9I9QOhhDM-M4i3P5lESb0cp2iI8,1595
18
- fastmcp/client/transports.py,sha256=liHLuOcgKotdlhTJBOiSn73WX0v7mnFa853hJBdu-2E,33898
18
+ fastmcp/client/transports.py,sha256=DNzvZGbe5q_rvk1C0bifejk7Gh561h-QTGGlyqwiSxM,33813
19
19
  fastmcp/client/auth/__init__.py,sha256=4DNsfp4iaQeBcpds0JDdMn6Mmfud44stWLsret0sVKY,91
20
20
  fastmcp/client/auth/bearer.py,sha256=MFEFqcH6u_V86msYiOsEFKN5ks1V9BnBNiPsPLHUTqo,399
21
21
  fastmcp/client/auth/oauth.py,sha256=pSyuI0FlRK1qkBA6mvq-bxKzl2B-pMCvPPzIByTJBEo,11745
@@ -48,12 +48,12 @@ fastmcp/server/elicitation.py,sha256=jZIHjV4NjhYbT-w8pBArwd0vNzP8OYwzmsnWDdk6Bd0
48
48
  fastmcp/server/http.py,sha256=d0Jij4HVTaAohluRXArSniXLb1HcHP3ytbe-mMHg6nE,11678
49
49
  fastmcp/server/low_level.py,sha256=LNmc_nU_wx-fRG8OEHdLPKopZpovcrWlyAxJzKss3TA,1239
50
50
  fastmcp/server/openapi.py,sha256=ALIbl0r2T1cvbSeFwz6HpIzut2akdngAtKDdWGyIWHs,36221
51
- fastmcp/server/proxy.py,sha256=Ofx7P5rU7mKVPDjvvIKjQS7_C1w4-1UiKc78x7AdjN8,15211
52
- fastmcp/server/server.py,sha256=XKt0vGwQNopBnYvciJtbJ_FVDt1uuwwuxrAcrVBJGa8,79703
51
+ fastmcp/server/proxy.py,sha256=NNyxtbOCJeKM-zRoaPCkVk26c7LHViHXNFfy95aXnAM,19591
52
+ fastmcp/server/server.py,sha256=KM1-dfwIoRiTYSLI5IvZ1Zy1imAkS3ILZ1QeRCgLNvY,81153
53
53
  fastmcp/server/auth/__init__.py,sha256=doHCLwOIElvH1NrTdpeP9JKfnNf3MDYPSpQfdsQ-uI0,84
54
54
  fastmcp/server/auth/auth.py,sha256=A00OKxglEMrGMMIiMbc6UmpGc2VoWDkEVU5g2pIzDIg,2119
55
55
  fastmcp/server/auth/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
- fastmcp/server/auth/providers/bearer.py,sha256=iRExt5hRKmrwr7MjsUN-UdQDe-iCSjH7sNCURaK7HBs,16456
56
+ fastmcp/server/auth/providers/bearer.py,sha256=rjwuKFgCm4fLsAQzUaIaE8OD6bml3sBaO7NYq3Wgs24,16718
57
57
  fastmcp/server/auth/providers/bearer_env.py,sha256=NYPCW363Q8u8BdiPPz1FdB3_kwmbCaWT5yKdAO-ZgwA,2081
58
58
  fastmcp/server/auth/providers/in_memory.py,sha256=Sb3GOtLL2bWbm8z-T8cEsMz1qcQUSHpPEEgYRvTOQi4,14251
59
59
  fastmcp/server/middleware/__init__.py,sha256=m1QJFQ7JW_2JHpJp1FurBNYaxbBUa_HyDn1Bw9mtyvc,367
@@ -68,19 +68,20 @@ fastmcp/tools/tool_manager.py,sha256=Sm_tOO-SY0m7tEN_dofP-tvBnC2HroPRKLU6sp8gnUw
68
68
  fastmcp/tools/tool_transform.py,sha256=YY2DZdJZ6fEGtgEP1Djrc49F8rAEyx6fgRGEyIGaxPE,32601
69
69
  fastmcp/utilities/__init__.py,sha256=-imJ8S-rXmbXMWeDamldP-dHDqAPg_wwmPVz-LNX14E,31
70
70
  fastmcp/utilities/cache.py,sha256=aV3oZ-ZhMgLSM9iAotlUlEy5jFvGXrVo0Y5Bj4PBtqY,707
71
+ fastmcp/utilities/cli.py,sha256=n3HA8IXg7hKaizuM3SRCW65UMXlqjUDf5USRSuscTdY,3287
71
72
  fastmcp/utilities/components.py,sha256=WIxNVZ7YxCLpdIm_pbTYeP0lAxikvgptVYhIL0LVmCc,2535
72
73
  fastmcp/utilities/exceptions.py,sha256=7Z9j5IzM5rT27BC1Mcn8tkS-bjqCYqMKwb2MMTaxJYU,1350
73
74
  fastmcp/utilities/http.py,sha256=1ns1ymBS-WSxbZjGP6JYjSO52Wa_ls4j4WbnXiupoa4,245
74
75
  fastmcp/utilities/inspect.py,sha256=XNA0dfYM5G-FVbJaVJO8loSUUCNypyLA-QjqTOneJyU,10833
75
76
  fastmcp/utilities/json_schema.py,sha256=K0QH5UazBD_tweBi-TguWYjUu5Lgp9wcM-wT42Fet5w,5022
76
77
  fastmcp/utilities/json_schema_type.py,sha256=Sml03nJGOnUfxCGrHWRMwZMultV0X5JThMepUnHIUiA,22377
77
- fastmcp/utilities/logging.py,sha256=B1WNO-ZWFjd9wiFSh13YtW1hAKaNmbpscDZleIAhr-g,1317
78
- fastmcp/utilities/mcp_config.py,sha256=ryjAfJUPquDSoKdSymPH4M2B0WvuM3pWUGI3cOgAX80,2782
78
+ fastmcp/utilities/logging.py,sha256=1y7oNmy8WrR0NsfNVw1LPoKu92OFdmzIO65syOKi_BI,1388
79
+ fastmcp/utilities/mcp_config.py,sha256=uGmkepcNpS_7HEuaK3465OcwcmebHGyvUddOjLfw1jY,3075
79
80
  fastmcp/utilities/openapi.py,sha256=neeaXwwn1OWdUp0Gawhx4SJHLfV78gXU5OMMTFGeD24,45235
80
- fastmcp/utilities/tests.py,sha256=O9hRSjnyaYQqu1RJ-CFBw1cIjezlwSQtS-Ea_iqO4sY,3899
81
- fastmcp/utilities/types.py,sha256=SWtzKpIr9TMeOE6TyPgqSi-SBXpWBPUnA5QPiP4nDzw,10512
82
- fastmcp-2.10.1.dist-info/METADATA,sha256=I0_44sR-kc7BcTHdA1mLy4f9zASLk6hCiOWjrcxBfk8,17796
83
- fastmcp-2.10.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
84
- fastmcp-2.10.1.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
85
- fastmcp-2.10.1.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
86
- fastmcp-2.10.1.dist-info/RECORD,,
81
+ fastmcp/utilities/tests.py,sha256=kZH8HQAC702a5vNJb4K0tO1ll9CZADWQ_P-5ERWSvSA,4242
82
+ fastmcp/utilities/types.py,sha256=c6HPvHCpkq8EXh0hWjaUlj9aCZklmxzAQHCXZy7llNo,10636
83
+ fastmcp-2.10.2.dist-info/METADATA,sha256=zvfV88PXJiHci0YJfQIrgPsaOvDaVsMVHUIHB6mtgCU,17796
84
+ fastmcp-2.10.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
85
+ fastmcp-2.10.2.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
86
+ fastmcp-2.10.2.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
87
+ fastmcp-2.10.2.dist-info/RECORD,,