2024-12-18 14:31:08 +00:00
|
|
|
"""Tests for example servers"""
|
2025-08-11 19:56:37 +02:00
|
|
|
# TODO(Marcelo): The `examples` directory needs to be importable as a package.
|
|
|
|
|
# pyright: reportMissingImports=false
|
|
|
|
|
# pyright: reportUnknownVariableType=false
|
|
|
|
|
# pyright: reportUnknownArgumentType=false
|
|
|
|
|
# pyright: reportUnknownMemberType=false
|
2024-12-19 22:33:40 +00:00
|
|
|
|
2025-05-14 15:43:59 +03:00
|
|
|
import sys
|
|
|
|
|
|
2024-12-18 14:31:08 +00:00
|
|
|
import pytest
|
2025-03-14 11:30:57 +00:00
|
|
|
from pytest_examples import CodeExample, EvalExample, find_examples
|
2024-12-19 22:33:40 +00:00
|
|
|
|
2025-08-11 19:56:37 +02:00
|
|
|
from mcp.shared.memory import create_connected_server_and_client_session as client_session
|
2024-12-21 01:16:10 +00:00
|
|
|
from mcp.types import TextContent, TextResourceContents
|
2024-12-19 22:33:40 +00:00
|
|
|
|
2024-12-18 14:31:08 +00:00
|
|
|
|
|
|
|
|
@pytest.mark.anyio
|
|
|
|
|
async def test_simple_echo():
|
|
|
|
|
"""Test the simple echo server"""
|
|
|
|
|
from examples.fastmcp.simple_echo import mcp
|
2024-12-19 22:33:40 +00:00
|
|
|
|
2024-12-18 14:31:08 +00:00
|
|
|
async with client_session(mcp._mcp_server) as client:
|
|
|
|
|
result = await client.call_tool("echo", {"text": "hello"})
|
|
|
|
|
assert len(result.content) == 1
|
|
|
|
|
content = result.content[0]
|
2024-12-19 23:19:41 +00:00
|
|
|
assert isinstance(content, TextContent)
|
2024-12-18 14:31:08 +00:00
|
|
|
assert content.text == "hello"
|
|
|
|
|
|
2024-12-19 22:33:40 +00:00
|
|
|
|
2024-12-18 14:31:08 +00:00
|
|
|
@pytest.mark.anyio
|
|
|
|
|
async def test_complex_inputs():
|
|
|
|
|
"""Test the complex inputs server"""
|
|
|
|
|
from examples.fastmcp.complex_inputs import mcp
|
2024-12-19 22:33:40 +00:00
|
|
|
|
2024-12-18 14:31:08 +00:00
|
|
|
async with client_session(mcp._mcp_server) as client:
|
2024-12-19 22:33:40 +00:00
|
|
|
tank = {"shrimp": [{"name": "bob"}, {"name": "alice"}]}
|
|
|
|
|
result = await client.call_tool("name_shrimp", {"tank": tank, "extra_names": ["charlie"]})
|
2024-12-18 14:31:08 +00:00
|
|
|
assert len(result.content) == 3
|
2024-12-19 23:19:41 +00:00
|
|
|
assert isinstance(result.content[0], TextContent)
|
|
|
|
|
assert isinstance(result.content[1], TextContent)
|
|
|
|
|
assert isinstance(result.content[2], TextContent)
|
2024-12-18 14:31:08 +00:00
|
|
|
assert result.content[0].text == "bob"
|
|
|
|
|
assert result.content[1].text == "alice"
|
|
|
|
|
assert result.content[2].text == "charlie"
|
|
|
|
|
|
2024-12-19 22:33:40 +00:00
|
|
|
|
2025-10-21 14:52:08 -04:00
|
|
|
@pytest.mark.anyio
|
|
|
|
|
async def test_direct_call_tool_result_return():
|
|
|
|
|
"""Test the CallToolResult echo server"""
|
|
|
|
|
from examples.fastmcp.direct_call_tool_result_return import mcp
|
|
|
|
|
|
|
|
|
|
async with client_session(mcp._mcp_server) as client:
|
|
|
|
|
result = await client.call_tool("echo", {"text": "hello"})
|
|
|
|
|
assert len(result.content) == 1
|
|
|
|
|
content = result.content[0]
|
|
|
|
|
assert isinstance(content, TextContent)
|
|
|
|
|
assert content.text == "hello"
|
|
|
|
|
assert result.structuredContent
|
|
|
|
|
assert result.structuredContent["text"] == "hello"
|
|
|
|
|
assert isinstance(result.meta, dict)
|
|
|
|
|
assert result.meta["some"] == "metadata"
|
|
|
|
|
|
|
|
|
|
|
2024-12-18 14:31:08 +00:00
|
|
|
@pytest.mark.anyio
|
2025-08-11 19:56:37 +02:00
|
|
|
async def test_desktop(monkeypatch: pytest.MonkeyPatch):
|
2024-12-18 14:31:08 +00:00
|
|
|
"""Test the desktop server"""
|
2024-12-21 01:16:10 +00:00
|
|
|
from pathlib import Path
|
|
|
|
|
|
2024-12-19 23:19:41 +00:00
|
|
|
from pydantic import AnyUrl
|
|
|
|
|
|
2024-12-18 14:31:08 +00:00
|
|
|
from examples.fastmcp.desktop import mcp
|
2024-12-19 22:33:40 +00:00
|
|
|
|
2024-12-21 01:16:10 +00:00
|
|
|
# Mock desktop directory listing
|
|
|
|
|
mock_files = [Path("/fake/path/file1.txt"), Path("/fake/path/file2.txt")]
|
2025-08-11 19:56:37 +02:00
|
|
|
monkeypatch.setattr(Path, "iterdir", lambda self: mock_files) # type: ignore[reportUnknownArgumentType]
|
2024-12-21 01:16:10 +00:00
|
|
|
monkeypatch.setattr(Path, "home", lambda: Path("/fake/home"))
|
|
|
|
|
|
2024-12-18 14:31:08 +00:00
|
|
|
async with client_session(mcp._mcp_server) as client:
|
2025-07-04 17:25:54 +01:00
|
|
|
# Test the sum function
|
|
|
|
|
result = await client.call_tool("sum", {"a": 1, "b": 2})
|
2024-12-18 14:31:08 +00:00
|
|
|
assert len(result.content) == 1
|
|
|
|
|
content = result.content[0]
|
2024-12-19 23:19:41 +00:00
|
|
|
assert isinstance(content, TextContent)
|
2024-12-18 14:31:08 +00:00
|
|
|
assert content.text == "3"
|
2024-12-19 22:33:40 +00:00
|
|
|
|
2024-12-18 14:31:08 +00:00
|
|
|
# Test the desktop resource
|
2024-12-19 23:19:41 +00:00
|
|
|
result = await client.read_resource(AnyUrl("dir://desktop"))
|
2024-12-18 14:31:08 +00:00
|
|
|
assert len(result.contents) == 1
|
|
|
|
|
content = result.contents[0]
|
2024-12-21 01:16:10 +00:00
|
|
|
assert isinstance(content, TextResourceContents)
|
2024-12-19 22:33:40 +00:00
|
|
|
assert isinstance(content.text, str)
|
2025-05-14 15:43:59 +03:00
|
|
|
if sys.platform == "win32":
|
|
|
|
|
file_1 = "/fake/path/file1.txt".replace("/", "\\\\") # might be a bug
|
|
|
|
|
file_2 = "/fake/path/file2.txt".replace("/", "\\\\") # might be a bug
|
|
|
|
|
assert file_1 in content.text
|
|
|
|
|
assert file_2 in content.text
|
|
|
|
|
# might be a bug, but the test is passing
|
|
|
|
|
else:
|
|
|
|
|
assert "/fake/path/file1.txt" in content.text
|
|
|
|
|
assert "/fake/path/file2.txt" in content.text
|
2025-03-14 11:30:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("example", find_examples("README.md"), ids=str)
|
|
|
|
|
def test_docs_examples(example: CodeExample, eval_example: EvalExample):
|
2025-07-04 17:25:54 +01:00
|
|
|
ruff_ignore: list[str] = ["F841", "I001", "F821"] # F821: undefined names (snippets lack imports)
|
2025-03-14 11:30:57 +00:00
|
|
|
|
2025-07-04 17:25:54 +01:00
|
|
|
# Use project's actual line length of 120
|
|
|
|
|
eval_example.set_config(ruff_ignore=ruff_ignore, target_version="py310", line_length=120)
|
2025-03-14 11:30:57 +00:00
|
|
|
|
2025-07-04 17:25:54 +01:00
|
|
|
# Use Ruff for both formatting and linting (skip Black)
|
2025-03-14 11:30:57 +00:00
|
|
|
if eval_example.update_examples: # pragma: no cover
|
2025-07-04 17:25:54 +01:00
|
|
|
eval_example.format_ruff(example)
|
2025-03-14 11:30:57 +00:00
|
|
|
else:
|
2025-07-04 17:25:54 +01:00
|
|
|
eval_example.lint_ruff(example)
|