Jeremiah Lowin commited on
Commit
08c7eb3
·
unverified ·
2 Parent(s): 26eef114a050cf

Merge pull request #1018 from jlowin/stateless-arg

Browse files
src/fastmcp/server/server.py CHANGED
@@ -1370,6 +1370,7 @@ class FastMCP(Generic[LifespanResultT]):
1370
  path: str | None = None,
1371
  uvicorn_config: dict[str, Any] | None = None,
1372
  middleware: list[ASGIMiddleware] | None = None,
 
1373
  ) -> None:
1374
  """Run the server using HTTP transport.
1375
 
@@ -1380,6 +1381,8 @@ class FastMCP(Generic[LifespanResultT]):
1380
  log_level: Log level for the server (defaults to settings.log_level)
1381
  path: Path for the endpoint (defaults to settings.streamable_http_path or settings.sse_path)
1382
  uvicorn_config: Additional configuration for the Uvicorn server
 
 
1383
  """
1384
 
1385
  host = host or self._deprecated_settings.host
@@ -1388,7 +1391,12 @@ class FastMCP(Generic[LifespanResultT]):
1388
  log_level or self._deprecated_settings.log_level
1389
  ).lower()
1390
 
1391
- app = self.http_app(path=path, transport=transport, middleware=middleware)
 
 
 
 
 
1392
 
1393
  # Get the path for the server URL
1394
  server_path = (
 
1370
  path: str | None = None,
1371
  uvicorn_config: dict[str, Any] | None = None,
1372
  middleware: list[ASGIMiddleware] | None = None,
1373
+ stateless_http: bool | None = None,
1374
  ) -> None:
1375
  """Run the server using HTTP transport.
1376
 
 
1381
  log_level: Log level for the server (defaults to settings.log_level)
1382
  path: Path for the endpoint (defaults to settings.streamable_http_path or settings.sse_path)
1383
  uvicorn_config: Additional configuration for the Uvicorn server
1384
+ middleware: A list of middleware to apply to the app
1385
+ stateless_http: Whether to use stateless HTTP (defaults to settings.stateless_http)
1386
  """
1387
 
1388
  host = host or self._deprecated_settings.host
 
1391
  log_level or self._deprecated_settings.log_level
1392
  ).lower()
1393
 
1394
+ app = self.http_app(
1395
+ path=path,
1396
+ transport=transport,
1397
+ middleware=middleware,
1398
+ stateless_http=stateless_http,
1399
+ )
1400
 
1401
  # Get the path for the server URL
1402
  server_path = (
src/fastmcp/settings.py CHANGED
@@ -1,6 +1,7 @@
1
  from __future__ import annotations as _annotations
2
 
3
  import inspect
 
4
  from pathlib import Path
5
  from typing import Annotated, Any, Literal
6
 
@@ -258,4 +259,21 @@ class Settings(BaseSettings):
258
  ] = None
259
 
260
 
261
- settings = Settings()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from __future__ import annotations as _annotations
2
 
3
  import inspect
4
+ import warnings
5
  from pathlib import Path
6
  from typing import Annotated, Any, Literal
7
 
 
259
  ] = None
260
 
261
 
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}'")
tests/client/test_streamable_http.py CHANGED
@@ -2,7 +2,7 @@ import asyncio
2
  import json
3
  import sys
4
  from collections.abc import AsyncGenerator
5
- from unittest.mock import AsyncMock
6
 
7
  import pytest
8
  import uvicorn
@@ -53,6 +53,7 @@ def fastmcp_server():
53
  async def greet_with_progress(name: str, ctx: Context) -> str:
54
  """Report progress for a greeting."""
55
  await ctx.report_progress(0.5, 1.0, "Greeting in progress")
 
56
  return f"Hello, {name}!"
57
 
58
  # Add a resource
@@ -108,8 +109,9 @@ def run_nested_server(host: str, port: int) -> None:
108
 
109
  @pytest.fixture()
110
  async def streamable_http_server(
111
- stateless_http: bool = False,
112
  ) -> AsyncGenerator[str, None]:
 
113
  with run_server_in_process(
114
  run_server, stateless_http=stateless_http, transport="http"
115
  ) as url:
@@ -176,16 +178,25 @@ async def test_greet_with_progress_tool(streamable_http_server: str):
176
  result = await client.call_tool("greet_with_progress", {"name": "Alice"})
177
  assert result.data == "Hello, Alice!"
178
 
179
- progress_handler.assert_called_once_with(0.5, 1.0, "Greeting in progress")
 
 
 
 
 
180
 
181
 
182
  @pytest.mark.parametrize("streamable_http_server", [True, False], indirect=True)
183
- async def test_elicitation_tool(streamable_http_server: str):
184
  """Test calling the elicitation tool in both stateless and stateful modes."""
185
 
186
  async def elicitation_handler(message, response_type, params, ctx):
187
  return {"value": "Alice"}
188
 
 
 
 
 
189
  async with Client(
190
  transport=StreamableHttpTransport(streamable_http_server),
191
  elicitation_handler=elicitation_handler,
 
2
  import json
3
  import sys
4
  from collections.abc import AsyncGenerator
5
+ from unittest.mock import AsyncMock, call
6
 
7
  import pytest
8
  import uvicorn
 
53
  async def greet_with_progress(name: str, ctx: Context) -> str:
54
  """Report progress for a greeting."""
55
  await ctx.report_progress(0.5, 1.0, "Greeting in progress")
56
+ await ctx.report_progress(0.75, 1.0, "Almost there!")
57
  return f"Hello, {name}!"
58
 
59
  # Add a resource
 
109
 
110
  @pytest.fixture()
111
  async def streamable_http_server(
112
+ request,
113
  ) -> AsyncGenerator[str, None]:
114
+ stateless_http = getattr(request, "param", False)
115
  with run_server_in_process(
116
  run_server, stateless_http=stateless_http, transport="http"
117
  ) as url:
 
178
  result = await client.call_tool("greet_with_progress", {"name": "Alice"})
179
  assert result.data == "Hello, Alice!"
180
 
181
+ progress_handler.assert_has_calls(
182
+ [
183
+ call(0.5, 1.0, "Greeting in progress"),
184
+ call(0.75, 1.0, "Almost there!"),
185
+ ]
186
+ )
187
 
188
 
189
  @pytest.mark.parametrize("streamable_http_server", [True, False], indirect=True)
190
+ async def test_elicitation_tool(streamable_http_server: str, request):
191
  """Test calling the elicitation tool in both stateless and stateful modes."""
192
 
193
  async def elicitation_handler(message, response_type, params, ctx):
194
  return {"value": "Alice"}
195
 
196
+ stateless_http = request.node.callspec.params.get("streamable_http_server", False)
197
+ if stateless_http:
198
+ pytest.xfail("Elicitation is not supported in stateless HTTP mode")
199
+
200
  async with Client(
201
  transport=StreamableHttpTransport(streamable_http_server),
202
  elicitation_handler=elicitation_handler,