Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/integrations/fastmcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ FastMcpConfig(
Enabled, each message is logged with its `method`, `source` and `type`, plus `duration` in
nanoseconds. A message that raises is logged at exception level and the exception is re-raised.

lite-bootstrap 2.0 requires fastmcp 4, where this log sees every message a client sends. That
includes notifications such as `notifications/initialized` and `notifications/cancelled`, the
`server/discover` request of a modern connection, and requests that fail before reaching a handler.
On fastmcp 3 it saw only routable requests.

Set `health_checks_enabled=False` to omit the health route.

## Sentry
Expand Down
11 changes: 5 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,10 @@ faststream-all = [
"lite-bootstrap[sentry,otl,logging,faststream,faststream-metrics,pyroscope]"
]
fastmcp = [
# >=3.0 for fastmcp.server.providers.Provider and FastMCP.add_provider()
# (FastMcpBootstrapper registers its teardown through them --
# docs/adr/0001-fastmcp-teardown-via-provider-lifespan.md).
"fastmcp>=3.0",
# fastmcp 3.0 pulls py-key-value-aio, which requires typing-extensions>=4.15.
# >=4.0 so one middleware dispatch model is supported: from 4.0 the access log's on_message sees
# every inbound message, notifications included. <5 until 5.x is tested.
"fastmcp>=4.0,<5",
# fastmcp 4.0 pulls py-key-value-aio 0.4.4, which requires typing-extensions>=4.15.
"typing-extensions>=4.15",
]
fastmcp-otl = [
Expand All @@ -188,7 +187,7 @@ fastmcp-otl = [
fastmcp-metrics = [
"lite-bootstrap[fastmcp]",
"prometheus-client>=0.20",
# >=8.0.1 to share the fastapi-metrics floor; it requires starlette>=1.0, which fastmcp 3.0 already pulls.
# >=8.0.1 to share the fastapi-metrics floor; it requires starlette>=1.0, which fastmcp 4.0 already pulls.
"prometheus-fastapi-instrumentator>=8.0.1",
]
fastmcp-all = [
Expand Down
19 changes: 19 additions & 0 deletions tests/test_fastmcp_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,25 @@ async def call_next(received: MiddlewareContext[typing.Any]) -> dict[str, str]:
assert isinstance(call_kwargs.kwargs["duration"], int)


async def test_fastmcp_access_log_records_notifications(monkeypatch: pytest.MonkeyPatch) -> None:
fake_logger = MagicMock()
monkeypatch.setattr(fastmcp_bootstrapper, "fastmcp_access_logger", fake_logger)
bootstrapper = FastMcpBootstrapper(bootstrap_config=_make_test_config(logging_access_log_enabled=True))
application = bootstrapper.bootstrap()
try:
async with Client(application, mode="legacy") as client:
await client.list_tools()
finally:
bootstrapper.teardown()

logged = [(call.kwargs["mcp"]["method"], call.kwargs["mcp"]["type"]) for call in fake_logger.info.call_args_list]
assert logged == [
("initialize", "request"),
("notifications/initialized", "notification"),
("tools/list", "request"),
]


async def test_fastmcp_logging_middleware_logs_exception(monkeypatch: pytest.MonkeyPatch) -> None:
fake_logger = MagicMock()
monkeypatch.setattr(
Expand Down
Loading