Skip to content

Commit 7193241

Browse files
authored
Raise the fastmcp floor to 4.0 and bound it below 5 (#374)
1 parent 02d3b22 commit 7193241

3 files changed

Lines changed: 29 additions & 6 deletions

File tree

‎docs/integrations/fastmcp.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ FastMcpConfig(
6262
Enabled, each message is logged with its `method`, `source` and `type`, plus `duration` in
6363
nanoseconds. A message that raises is logged at exception level and the exception is re-raised.
6464

65+
lite-bootstrap 2.0 requires fastmcp 4, where this log sees every message a client sends. That
66+
includes notifications such as `notifications/initialized` and `notifications/cancelled`, the
67+
`server/discover` request of a modern connection, and requests that fail before reaching a handler.
68+
On fastmcp 3 it saw only routable requests.
69+
6570
Set `health_checks_enabled=False` to omit the health route.
6671

6772
## Sentry

‎pyproject.toml‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,10 @@ faststream-all = [
174174
"lite-bootstrap[sentry,otl,logging,faststream,faststream-metrics,pyroscope]"
175175
]
176176
fastmcp = [
177-
# >=3.0 for fastmcp.server.providers.Provider and FastMCP.add_provider()
178-
# (FastMcpBootstrapper registers its teardown through them --
179-
# docs/adr/0001-fastmcp-teardown-via-provider-lifespan.md).
180-
"fastmcp>=3.0",
181-
# fastmcp 3.0 pulls py-key-value-aio, which requires typing-extensions>=4.15.
177+
# >=4.0 so one middleware dispatch model is supported: from 4.0 the access log's on_message sees
178+
# every inbound message, notifications included. <5 until 5.x is tested.
179+
"fastmcp>=4.0,<5",
180+
# fastmcp 4.0 pulls py-key-value-aio 0.4.4, which requires typing-extensions>=4.15.
182181
"typing-extensions>=4.15",
183182
]
184183
fastmcp-otl = [
@@ -188,7 +187,7 @@ fastmcp-otl = [
188187
fastmcp-metrics = [
189188
"lite-bootstrap[fastmcp]",
190189
"prometheus-client>=0.20",
191-
# >=8.0.1 to share the fastapi-metrics floor; it requires starlette>=1.0, which fastmcp 3.0 already pulls.
190+
# >=8.0.1 to share the fastapi-metrics floor; it requires starlette>=1.0, which fastmcp 4.0 already pulls.
192191
"prometheus-fastapi-instrumentator>=8.0.1",
193192
]
194193
fastmcp-all = [

‎tests/test_fastmcp_bootstrap.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,25 @@ async def call_next(received: MiddlewareContext[typing.Any]) -> dict[str, str]:
131131
assert isinstance(call_kwargs.kwargs["duration"], int)
132132

133133

134+
async def test_fastmcp_access_log_records_notifications(monkeypatch: pytest.MonkeyPatch) -> None:
135+
fake_logger = MagicMock()
136+
monkeypatch.setattr(fastmcp_bootstrapper, "fastmcp_access_logger", fake_logger)
137+
bootstrapper = FastMcpBootstrapper(bootstrap_config=_make_test_config(logging_access_log_enabled=True))
138+
application = bootstrapper.bootstrap()
139+
try:
140+
async with Client(application, mode="legacy") as client:
141+
await client.list_tools()
142+
finally:
143+
bootstrapper.teardown()
144+
145+
logged = [(call.kwargs["mcp"]["method"], call.kwargs["mcp"]["type"]) for call in fake_logger.info.call_args_list]
146+
assert logged == [
147+
("initialize", "request"),
148+
("notifications/initialized", "notification"),
149+
("tools/list", "request"),
150+
]
151+
152+
134153
async def test_fastmcp_logging_middleware_logs_exception(monkeypatch: pytest.MonkeyPatch) -> None:
135154
fake_logger = MagicMock()
136155
monkeypatch.setattr(

0 commit comments

Comments
 (0)