rm slop markers

This commit is contained in:
zzstoatzz 2025-05-20 13:48:23 -05:00
commit 7f46580c14
10 changed files with 171 additions and 202 deletions

View file

@ -1,50 +0,0 @@
name: Run integration tests
env:
# enable colored output
PY_COLORS: 1
on:
push:
branches: ["main"]
paths:
- "src/**"
- "integration_tests/**"
- "uv.lock"
- "pyproject.toml"
- ".github/workflows/**"
# run on all pull requests because these checks are required and will block merges otherwise
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
run_tests:
name: "Run tests: Python ${{ matrix.python-version }} on ${{ matrix.os }}"
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
python-version: ["3.10"]
fail-fast: false
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
cache-dependency-glob: "uv.lock"
python-version: ${{ matrix.python-version }}
- name: Install FastMCP
run: uv sync --locked
- name: Run tests
run: uv run pytest integration_tests

View file

@ -1,89 +0,0 @@
import os
import subprocess
import sys
import tempfile
import time
import httpx
import pytest
PYTHON_EXE = sys.executable
SERVER_CODE = """
import uvicorn
from fastapi import FastAPI
from fastmcp import FastMCP
mcp = FastMCP()
@mcp.tool("dummy_tool", "A simple dummy tool for the test server")
def add(a: int, b: int) -> int:
return a + b
app = FastAPI() # Intentionally no lifespan=mcp.lifespan
app.mount("/", mcp.http_app(transport="streamable-http"))
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8080, log_config=None)
"""
@pytest.mark.timeout(20)
def test_server_shows_informative_error_on_stderr():
"""
Runs a minimal FastMCP+FastAPI server (that omits lifespan wiring)
as a subprocess, triggers the error via an HTTP request, and then checks
if the server's stderr contains the specific informative error message for issue #518.
"""
process = None
captured_stderr = ""
with tempfile.NamedTemporaryFile(
mode="w", suffix=".py", delete=False
) as tmp_script:
tmp_script.write(SERVER_CODE)
tmp_script_path = tmp_script.name
try:
process = subprocess.Popen(
[PYTHON_EXE, "-u", tmp_script_path],
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
text=True,
universal_newlines=True,
)
time.sleep(3)
if process.poll() is None:
try:
with httpx.Client(timeout=5.0) as client:
# The mounted FastMCP app is at root, its internal default path is /mcp
client.get("http://localhost:8080/mcp/")
except httpx.RequestError:
pass
time.sleep(1)
finally:
if process:
if process.poll() is None:
process.terminate()
try:
_, captured_stderr = process.communicate(timeout=10)
except subprocess.TimeoutExpired:
process.kill()
_, captured_stderr = process.communicate()
if os.path.exists(tmp_script_path):
os.unlink(tmp_script_path)
assert captured_stderr is not None, "stderr should have been captured"
normalized_stderr = captured_stderr.replace("\r\n", "\n").replace("\r", "\n")
assert (
"FastMCP's StreamableHTTPSessionManager task group was not initialized"
in normalized_stderr
)
assert "lifespan=mcp_app.lifespan" in normalized_stderr
assert "gofastmcp.com/deployment/asgi" in normalized_stderr
assert "Original error: Task group is not initialized" in normalized_stderr

View file

@ -71,7 +71,7 @@ def _run_server(mcp_server: FastMCP, transport: Literal["sse"], port: int) -> No
@contextmanager
def run_server_in_process(
server_fn: Callable[[str, int], None], *args
server_fn: Callable[..., None], *args
) -> Generator[str, None, None]:
"""
Context manager that runs a Starlette app in a separate process and returns the
@ -109,7 +109,11 @@ def run_server_in_process(
yield f"http://{host}:{port}"
proc.kill()
proc.join(timeout=2)
proc.terminate()
proc.join(timeout=5)
if proc.is_alive():
raise RuntimeError("Server process failed to terminate")
# If it's still alive, then force kill it
proc.kill()
proc.join(timeout=2)
if proc.is_alive():
raise RuntimeError("Server process failed to terminate even after kill")

View file

@ -85,7 +85,6 @@ ERROR_TOOL_NAME = "error_tool"
NO_RETURN_TOOL_NAME = "no_return_tool"
@pytest.mark.asyncio
async def test_call_tool_bulk_single_success(bulk_caller_live: BulkToolCaller):
"""Test single successful call via call_tool_bulk using echo_tool."""
tool_arguments = [{"arg1": "value1"}]
@ -98,7 +97,6 @@ async def test_call_tool_bulk_single_success(bulk_caller_live: BulkToolCaller):
assert result == expected_result
@pytest.mark.asyncio
async def test_call_tool_bulk_multiple_success(bulk_caller_live: BulkToolCaller):
"""Test multiple successful calls via call_tool_bulk using echo_tool."""
tool_arguments = [{"arg1": "value1"}, {"arg1": "value2"}]
@ -110,7 +108,6 @@ async def test_call_tool_bulk_multiple_success(bulk_caller_live: BulkToolCaller)
assert results == expected_results
@pytest.mark.asyncio
async def test_call_tool_bulk_error_stops(bulk_caller_live: BulkToolCaller):
"""Test call_tool_bulk stops on first error using error_tool."""
tool_arguments = [{"arg1": "error_value"}, {"arg1": "value2"}]
@ -125,7 +122,6 @@ async def test_call_tool_bulk_error_stops(bulk_caller_live: BulkToolCaller):
assert result == expected_result
@pytest.mark.asyncio
async def test_call_tool_bulk_error_continues(bulk_caller_live: BulkToolCaller):
"""Test call_tool_bulk continues on error using error_tool and echo_tool."""
tool_arguments = [{"arg1": "error_value"}, {"arg1": "success_value"}]
@ -148,7 +144,6 @@ async def test_call_tool_bulk_error_continues(bulk_caller_live: BulkToolCaller):
assert success_result == expected_success_result
@pytest.mark.asyncio
async def test_call_tools_bulk_single_success(bulk_caller_live: BulkToolCaller):
"""Test single successful call via call_tools_bulk using echo_tool."""
tool_calls = [CallToolRequest(tool=ECHO_TOOL_NAME, arguments={"arg1": "value1"})]
@ -161,7 +156,6 @@ async def test_call_tools_bulk_single_success(bulk_caller_live: BulkToolCaller):
assert result == expected_result
@pytest.mark.asyncio
async def test_call_tools_bulk_multiple_success(bulk_caller_live: BulkToolCaller):
"""Test multiple successful calls via call_tools_bulk with different tools."""
tool_calls = [
@ -181,7 +175,6 @@ async def test_call_tools_bulk_multiple_success(bulk_caller_live: BulkToolCaller
assert results == expected_results
@pytest.mark.asyncio
async def test_call_tools_bulk_error_stops(bulk_caller_live: BulkToolCaller):
"""Test call_tools_bulk stops on first error using error_tool."""
tool_calls = [
@ -199,7 +192,6 @@ async def test_call_tools_bulk_error_stops(bulk_caller_live: BulkToolCaller):
assert result == expected_result
@pytest.mark.asyncio
async def test_call_tools_bulk_error_continues(bulk_caller_live: BulkToolCaller):
"""Test call_tools_bulk continues on error using error_tool and echo_tool."""
tool_calls = [

View file

@ -341,7 +341,6 @@ async def tokens(test_client, registered_client, auth_code, pkce_challenge, requ
class TestAuthEndpoints:
@pytest.mark.anyio
async def test_metadata_endpoint(self, test_client: httpx.AsyncClient):
"""Test the OAuth 2.0 metadata endpoint."""
print("Sending request to metadata endpoint")
@ -370,7 +369,6 @@ class TestAuthEndpoints:
]
assert metadata["service_documentation"] == "https://docs.example.com/"
@pytest.mark.anyio
async def test_token_validation_error(self, test_client: httpx.AsyncClient):
"""Test token endpoint error - validation error."""
# Missing required fields
@ -387,7 +385,6 @@ class TestAuthEndpoints:
"error_description" in error_response
) # Contains validation error messages
@pytest.mark.anyio
async def test_token_invalid_auth_code(
self, test_client, registered_client, pkce_challenge
):
@ -414,7 +411,6 @@ class TestAuthEndpoints:
"authorization code does not exist" in error_response["error_description"]
)
@pytest.mark.anyio
async def test_token_expired_auth_code(
self,
test_client,
@ -459,7 +455,6 @@ class TestAuthEndpoints:
"authorization code has expired" in error_response["error_description"]
)
@pytest.mark.anyio
@pytest.mark.parametrize(
"registered_client",
[
@ -494,7 +489,6 @@ class TestAuthEndpoints:
assert error_response["error"] == "invalid_request"
assert "redirect_uri did not match" in error_response["error_description"]
@pytest.mark.anyio
async def test_token_code_verifier_mismatch(
self, test_client, registered_client, auth_code
):
@ -517,7 +511,6 @@ class TestAuthEndpoints:
assert error_response["error"] == "invalid_grant"
assert "incorrect code_verifier" in error_response["error_description"]
@pytest.mark.anyio
async def test_token_invalid_refresh_token(self, test_client, registered_client):
"""Test token endpoint error - refresh token does not exist."""
# Try to use a non-existent refresh token
@ -535,7 +528,6 @@ class TestAuthEndpoints:
assert error_response["error"] == "invalid_grant"
assert "refresh token does not exist" in error_response["error_description"]
@pytest.mark.anyio
async def test_token_expired_refresh_token(
self,
test_client,
@ -586,7 +578,6 @@ class TestAuthEndpoints:
assert error_response["error"] == "invalid_grant"
assert "refresh token has expired" in error_response["error_description"]
@pytest.mark.anyio
async def test_token_invalid_scope(
self, test_client, registered_client, auth_code, pkce_challenge
):
@ -624,7 +615,6 @@ class TestAuthEndpoints:
assert error_response["error"] == "invalid_scope"
assert "cannot request scope" in error_response["error_description"]
@pytest.mark.anyio
async def test_client_registration(
self, test_client: httpx.AsyncClient, mock_oauth_provider: MockOAuthProvider
):
@ -652,7 +642,6 @@ class TestAuthEndpoints:
# client_info["client_id"]
# ) is not None
@pytest.mark.anyio
async def test_client_registration_missing_required_fields(
self, test_client: httpx.AsyncClient
):
@ -673,7 +662,6 @@ class TestAuthEndpoints:
assert error_data["error"] == "invalid_client_metadata"
assert error_data["error_description"] == "redirect_uris: Field required"
@pytest.mark.anyio
async def test_client_registration_invalid_uri(
self, test_client: httpx.AsyncClient
):
@ -696,7 +684,6 @@ class TestAuthEndpoints:
"redirect_uris.0: Input should be a valid URL, relative URL without a base"
)
@pytest.mark.anyio
async def test_client_registration_empty_redirect_uris(
self, test_client: httpx.AsyncClient
):
@ -719,7 +706,6 @@ class TestAuthEndpoints:
== "redirect_uris: List should have at least 1 item after validation, not 0"
)
@pytest.mark.anyio
async def test_authorize_form_post(
self,
test_client: httpx.AsyncClient,
@ -763,7 +749,6 @@ class TestAuthEndpoints:
assert "code" in query_params
assert query_params["state"][0] == "test_form_state"
@pytest.mark.anyio
async def test_authorization_get(
self,
test_client: httpx.AsyncClient,
@ -878,7 +863,6 @@ class TestAuthEndpoints:
is None
)
@pytest.mark.anyio
async def test_revoke_invalid_token(self, test_client, registered_client):
"""Test revoking an invalid token."""
response = await test_client.post(
@ -892,7 +876,6 @@ class TestAuthEndpoints:
# per RFC, this should return 200 even if the token is invalid
assert response.status_code == 200
@pytest.mark.anyio
async def test_revoke_with_malformed_token(self, test_client, registered_client):
response = await test_client.post(
"/revoke",
@ -908,7 +891,6 @@ class TestAuthEndpoints:
assert error_response["error"] == "invalid_request"
assert "token_type_hint" in error_response["error_description"]
@pytest.mark.anyio
async def test_client_registration_disallowed_scopes(
self, test_client: httpx.AsyncClient
):
@ -930,7 +912,6 @@ class TestAuthEndpoints:
assert "scope" in error_data["error_description"]
assert "admin" in error_data["error_description"]
@pytest.mark.anyio
async def test_client_registration_default_scopes(
self, test_client: httpx.AsyncClient, mock_oauth_provider: MockOAuthProvider
):
@ -959,7 +940,6 @@ class TestAuthEndpoints:
# Check that default scopes were applied
assert registered_client.scope == "read write"
@pytest.mark.anyio
async def test_client_registration_invalid_grant_type(
self, test_client: httpx.AsyncClient
):
@ -986,7 +966,6 @@ class TestAuthEndpoints:
class TestAuthorizeEndpointErrors:
"""Test error handling in the OAuth authorization endpoint."""
@pytest.mark.anyio
async def test_authorize_missing_client_id(
self, test_client: httpx.AsyncClient, pkce_challenge
):
@ -1012,7 +991,6 @@ class TestAuthorizeEndpointErrors:
# The response should include an error message about missing client_id
assert "client_id" in response.text.lower()
@pytest.mark.anyio
async def test_authorize_invalid_client_id(
self, test_client: httpx.AsyncClient, pkce_challenge
):
@ -1038,7 +1016,6 @@ class TestAuthorizeEndpointErrors:
# The response should include an error message about invalid client_id
assert "client" in response.text.lower()
@pytest.mark.anyio
async def test_authorize_missing_redirect_uri(
self, test_client: httpx.AsyncClient, registered_client, pkce_challenge
):
@ -1064,7 +1041,6 @@ class TestAuthorizeEndpointErrors:
redirect_url = response.headers["location"]
assert redirect_url.startswith("https://client.example.com/callback")
@pytest.mark.anyio
async def test_authorize_invalid_redirect_uri(
self, test_client: httpx.AsyncClient, registered_client, pkce_challenge
):
@ -1092,7 +1068,6 @@ class TestAuthorizeEndpointErrors:
# The response should include an error message about redirect_uri mismatch
assert "redirect" in response.text.lower()
@pytest.mark.anyio
@pytest.mark.parametrize(
"registered_client",
[
@ -1130,7 +1105,6 @@ class TestAuthorizeEndpointErrors:
# The response should include an error message about missing redirect_uri
assert "redirect_uri" in response.text.lower()
@pytest.mark.anyio
async def test_authorize_unsupported_response_type(
self, test_client: httpx.AsyncClient, registered_client, pkce_challenge
):
@ -1164,7 +1138,6 @@ class TestAuthorizeEndpointErrors:
assert "state" in query_params
assert query_params["state"][0] == "test_state"
@pytest.mark.anyio
async def test_authorize_missing_response_type(
self, test_client: httpx.AsyncClient, registered_client, pkce_challenge
):
@ -1197,7 +1170,6 @@ class TestAuthorizeEndpointErrors:
assert "state" in query_params
assert query_params["state"][0] == "test_state"
@pytest.mark.anyio
async def test_authorize_missing_pkce_challenge(
self, test_client: httpx.AsyncClient, registered_client
):
@ -1228,7 +1200,6 @@ class TestAuthorizeEndpointErrors:
assert "state" in query_params
assert query_params["state"][0] == "test_state"
@pytest.mark.anyio
async def test_authorize_invalid_scope(
self, test_client: httpx.AsyncClient, registered_client, pkce_challenge
):

View file

@ -4,7 +4,6 @@ from collections.abc import Callable
from typing import Any
import httpx
import pytest
from httpx import ASGITransport
from starlette.middleware import Middleware
from starlette.middleware.base import BaseHTTPMiddleware
@ -51,7 +50,6 @@ async def endpoint_handler(request: Request):
return JSONResponse({"message": "Hello, world!"})
@pytest.mark.asyncio
async def test_sse_app_with_custom_middleware():
"""Test that custom middleware works with SSE app."""
server = FastMCP(name="TestServer")
@ -82,7 +80,6 @@ async def test_sse_app_with_custom_middleware():
assert response.headers["X-Custom-Header"] == "test-value"
@pytest.mark.asyncio
async def test_streamable_http_app_with_custom_middleware():
"""Test that custom middleware works with StreamableHTTP app."""
server = FastMCP(name="TestServer")
@ -113,7 +110,6 @@ async def test_streamable_http_app_with_custom_middleware():
assert response.headers["X-Custom-Header"] == "test-value"
@pytest.mark.asyncio
async def test_create_sse_app_with_custom_middleware():
"""Test that custom middleware works with create_sse_app function."""
server = FastMCP(name="TestServer")
@ -149,7 +145,6 @@ async def test_create_sse_app_with_custom_middleware():
assert data["state"]["modified_by"] == "middleware"
@pytest.mark.asyncio
async def test_create_streamable_http_app_with_custom_middleware():
"""Test that custom middleware works with create_streamable_http_app function."""
server = FastMCP(name="TestServer")
@ -184,7 +179,6 @@ async def test_create_streamable_http_app_with_custom_middleware():
assert data["state"]["modified_by"] == "middleware"
@pytest.mark.asyncio
async def test_multiple_middleware_ordering():
"""Test that multiple middleware are applied in the correct order."""
server = FastMCP(name="TestServer")

View file

@ -1,10 +1,15 @@
"""Tests for lifespan functionality in both low-level and FastMCP servers."""
import os
import sys
import traceback
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from pathlib import Path
import anyio
import pytest
import httpx
import uvicorn
from mcp.server.lowlevel.server import NotificationOptions, Server
from mcp.server.models import InitializationOptions
from mcp.shared.message import SessionMessage
@ -17,11 +22,13 @@ from mcp.types import (
JSONRPCRequest,
)
from pydantic import TypeAdapter
from starlette.applications import Starlette
from starlette.routing import Mount
from fastmcp import Context, FastMCP
from fastmcp.utilities.tests import run_server_in_process
@pytest.mark.anyio
async def test_lowlevel_server_lifespan():
"""Test that lifespan works in low-level server."""
@ -132,7 +139,6 @@ async def test_lowlevel_server_lifespan():
tg.cancel_scope.cancel()
@pytest.mark.anyio
async def test_fastmcp_server_lifespan():
"""Test that lifespan works in FastMCP server."""
@ -234,3 +240,157 @@ async def test_fastmcp_server_lifespan():
# Cancel server task
tg.cancel_scope.cancel()
def run_server_with_incorrect_lifespan_setup(
host: str, port: int, server_log_file_path: str
) -> None:
os.makedirs(os.path.dirname(server_log_file_path), exist_ok=True)
CUSTOM_LOGGING_CONFIG = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"default": {
"()": "uvicorn.logging.DefaultFormatter",
"fmt": "%(levelprefix)s %(asctime)s [%(name)s] %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S",
"use_colors": False,
},
"access": {
"()": "uvicorn.logging.AccessFormatter",
"fmt": '%(levelprefix)s %(asctime)s [%(name)s] %(client_addr)s - "%(request_line)s" %(status_code)s',
"datefmt": "%Y-%m-%d %H:%M:%S",
"use_colors": False,
},
},
"handlers": {
"file_default": {
"formatter": "default",
"class": "logging.FileHandler",
"filename": server_log_file_path,
"mode": "w",
},
"file_access": {
"formatter": "access",
"class": "logging.FileHandler",
"filename": server_log_file_path,
"mode": "a",
},
},
"loggers": {
"uvicorn": { # Catches uvicorn root logs
"handlers": ["file_default"],
"level": "DEBUG",
"propagate": False,
},
"uvicorn.error": {
"handlers": ["file_default"],
"level": "DEBUG",
"propagate": False,
},
"uvicorn.access": {
"handlers": ["file_access"],
"level": "INFO",
"propagate": False,
},
},
"root": {
"handlers": ["file_default"],
"level": "DEBUG",
},
}
try:
mcp = FastMCP()
@mcp.tool("ping_tool", "A simple ping tool for the test server")
def ping_tool() -> str:
return "pong"
mcp_asgi_app = mcp.http_app(transport="streamable-http")
parent_app = Starlette(
routes=[Mount("/mounted_mcp", app=mcp_asgi_app)],
)
uvicorn.run(
parent_app,
host=host,
port=port,
log_config=CUSTOM_LOGGING_CONFIG,
log_level=None,
)
sys.exit(0)
except Exception as e_outer:
with open(server_log_file_path, "a") as f_fallback:
f_fallback.write(
"--- FALLBACK EXCEPTION IN SERVER RUNNER (PRE-UVICORN) ---\n"
)
f_fallback.write(f"{type(e_outer).__name__}: {e_outer}\n")
f_fallback.write(traceback.format_exc())
sys.exit(1)
async def test_missing_lifespan_logs_informative_error(tmp_path: Path):
server_log_file = tmp_path / "server.log"
with run_server_in_process(
run_server_with_incorrect_lifespan_setup, str(server_log_file)
) as server_url:
full_mcp_path = server_url + "/mounted_mcp/mcp/"
client_triggered_error = False
response_status = -1
response_body = ""
try:
async with httpx.AsyncClient(timeout=10) as client:
response = await client.post(
full_mcp_path,
json={"id": 1, "method": "list_tools", "jsonrpc": "2.0"},
)
response_status = response.status_code
response_body = response.text
if response.status_code == 500:
client_triggered_error = True
else:
print(
f"Client received unexpected status code: {response.status_code} "
f"Response: {response_body[:500]}"
)
except httpx.RequestError as e:
print(f"Client request failed with RequestError: {e}")
client_triggered_error = True
assert client_triggered_error, (
f"Client request did not result in a 500 error or a request error. "
f"Status: {response_status}, Body: {response_body[:500]}"
)
assert server_log_file.exists(), (
f"Server log file was not created at {server_log_file}"
)
log_content = server_log_file.read_text()
print(f"--- Captured Server Log Content ({server_log_file}) ---")
print(log_content)
print("--- End Server Log Content ---")
# Core assertions for the enhanced error message
assert (
"FastMCP's StreamableHTTPSessionManager task group was not initialized"
in log_content
)
assert "lifespan=mcp_app.lifespan" in log_content
assert "gofastmcp.com/deployment/asgi" in log_content
assert "Original error: Task group is not initialized" in log_content
# Check for Uvicorn's own error logging wrapper for the request
assert "ERROR" in log_content # General check for ERROR level logs
assert "Exception in ASGI application" in log_content
# Sanity checks for server operation and logging setup
assert "Uvicorn running on" in log_content
assert (
"--- FALLBACK EXCEPTION IN SERVER RUNNER (PRE-UVICORN) ---" not in log_content
)

View file

@ -87,7 +87,6 @@ async def test_fastmcp_from_openapi(array_path_spec, mock_client):
assert "test-operation" in tool_names
@pytest.mark.asyncio
async def test_array_path_parameter_handling(mock_client):
"""Test how array path parameters are handled."""
# Create a simple route with array path parameter
@ -158,7 +157,6 @@ async def test_array_path_parameter_handling(mock_client):
)
@pytest.mark.asyncio
async def test_integration_array_path_parameter(array_path_spec, mock_client):
"""Integration test for array path parameters."""
# Create FastMCP from the spec
@ -192,7 +190,6 @@ async def test_integration_array_path_parameter(array_path_spec, mock_client):
)
@pytest.mark.asyncio
async def test_complex_nested_array_path_parameter(mock_client):
"""Test handling of complex nested array path parameters."""
# Create a route with a path parameter that contains nested objects in an array
@ -262,7 +259,6 @@ async def test_complex_nested_array_path_parameter(mock_client):
assert "{" not in called_url, "The URL should not contain Python object syntax"
@pytest.mark.asyncio
async def test_array_query_param_with_fastapi():
"""Test array query parameters using FastAPI and FastMCP.from_fastapi integration."""
# Create a FastAPI app with a route that has an array query parameter
@ -323,7 +319,6 @@ async def test_array_query_param_with_fastapi():
assert result_data == {"selected": ["monday", "tuesday"]}
@pytest.mark.asyncio
async def test_array_query_parameter_format(mock_client):
"""Test that array query parameters are formatted as comma-separated values when explode=False."""
# Create a route with array query parameter
@ -394,7 +389,6 @@ async def test_array_query_parameter_format(mock_client):
)
@pytest.mark.asyncio
async def test_array_query_parameter_exploded_format(mock_client):
"""Test that array query parameters are formatted as separate parameters when explode=True."""
# Create a route with array query parameter with explode=True (default)

View file

@ -40,7 +40,6 @@ def test_streamable_http_app_deprecation_warning():
assert isinstance(app, Starlette)
@pytest.mark.asyncio
async def test_run_sse_async_deprecation_warning():
"""Test that run_sse_async raises a deprecation warning."""
server = FastMCP("TestServer")
@ -58,7 +57,6 @@ async def test_run_sse_async_deprecation_warning():
assert call_kwargs.get("transport") == "sse"
@pytest.mark.asyncio
async def test_run_streamable_http_async_deprecation_warning():
"""Test that run_streamable_http_async raises a deprecation warning."""
server = FastMCP("TestServer")

View file

@ -1,6 +1,5 @@
"""Tests for example servers"""
import pytest
from mcp.types import (
PromptMessage,
TextContent,
@ -11,7 +10,6 @@ from pydantic import AnyUrl
from fastmcp import Client
@pytest.mark.anyio
async def test_simple_echo():
"""Test the simple echo server"""
from examples.simple_echo import mcp
@ -23,7 +21,6 @@ async def test_simple_echo():
assert result[0].text == "hello"
@pytest.mark.anyio
async def test_complex_inputs():
"""Test the complex inputs server"""
from examples.complex_inputs import mcp
@ -38,7 +35,6 @@ async def test_complex_inputs():
assert result[0].text == '[\n "bob",\n "alice",\n "charlie"\n]'
@pytest.mark.anyio
async def test_desktop(monkeypatch):
"""Test the desktop server"""
from examples.desktop import mcp
@ -58,7 +54,6 @@ async def test_desktop(monkeypatch):
assert result[0].text == "Hello, rooter12!"
@pytest.mark.anyio
async def test_echo():
"""Test the echo server"""
from examples.echo import mcp