Spaces:
Running
Running
File size: 8,281 Bytes
ef1072a b90e63c 8e64604 ef1072a ca11c0a b90e63c ca11c0a ef1072a ca11c0a ef1072a 8e64604 ef1072a ca11c0a ef1072a 8e64604 ef1072a 8e64604 ef1072a 8e64604 ef1072a b90e63c ef1072a ca11c0a b476cc6 ca11c0a b476cc6 ca11c0a b476cc6 ca11c0a b476cc6 ca11c0a a4ec518 b476cc6 ca11c0a b476cc6 ca11c0a a4ec518 b476cc6 a4ec518 b476cc6 a4ec518 b476cc6 a4ec518 b476cc6 a4ec518 b476cc6 ca11c0a b476cc6 ca11c0a b476cc6 ca11c0a b476cc6 ca11c0a a4ec518 b476cc6 ca11c0a a4ec518 b476cc6 ca11c0a b476cc6 a4ec518 b476cc6 a4ec518 b476cc6 ca11c0a a4ec518 ca11c0a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 | import inspect
import json
from pathlib import Path
import pytest
from pydantic import ValidationError
from fastmcp.cli.run import (
create_mcp_config_server,
import_server,
is_url,
parse_file_path,
)
from fastmcp.client.client import Client
from fastmcp.client.transports import FastMCPTransport
from fastmcp.mcp_config import MCPConfig, StdioMCPServer
from fastmcp.server.server import FastMCP
class TestUrlDetection:
"""Test URL detection functionality."""
def test_is_url_valid_http(self):
"""Test detection of valid HTTP URLs."""
assert is_url("http://example.com")
assert is_url("http://localhost:8080")
assert is_url("http://127.0.0.1:3000/path")
def test_is_url_valid_https(self):
"""Test detection of valid HTTPS URLs."""
assert is_url("https://example.com")
assert is_url("https://api.example.com/mcp")
assert is_url("https://localhost:8443")
def test_is_url_invalid(self):
"""Test detection of non-URLs."""
assert not is_url("server.py")
assert not is_url("/path/to/server.py")
assert not is_url("server.py:app")
assert not is_url("ftp://example.com") # Not http/https
assert not is_url("file:///path/to/file")
class TestFilePathParsing:
"""Test file path parsing functionality."""
def test_parse_file_path_simple(self, tmp_path):
"""Test parsing simple file path without object."""
test_file = tmp_path / "server.py"
test_file.write_text("# test server")
file_path, server_object = parse_file_path(str(test_file))
assert file_path == test_file.resolve()
assert server_object is None
def test_parse_file_path_with_object(self, tmp_path):
"""Test parsing file path with object specification."""
test_file = tmp_path / "server.py"
test_file.write_text("# test server")
file_path, server_object = parse_file_path(f"{test_file}:app")
assert file_path == test_file.resolve()
assert server_object == "app"
def test_parse_file_path_complex_object(self, tmp_path):
"""Test parsing file path with complex object specification."""
test_file = tmp_path / "server.py"
test_file.write_text("# test server")
# The current implementation splits on the last colon, so file:module:app
# becomes file_path="file:module" and server_object="app"
# We need to create a file with a colon in the name for this test
complex_file = tmp_path / "server:module.py"
complex_file.write_text("# test server")
file_path, server_object = parse_file_path(f"{complex_file}:app")
assert file_path == complex_file.resolve()
assert server_object == "app"
def test_parse_file_path_nonexistent(self):
"""Test parsing nonexistent file path exits."""
with pytest.raises(SystemExit) as exc_info:
parse_file_path("nonexistent.py")
assert exc_info.value.code == 1
def test_parse_file_path_directory(self, tmp_path):
"""Test parsing directory path exits."""
with pytest.raises(SystemExit) as exc_info:
parse_file_path(str(tmp_path))
assert exc_info.value.code == 1
class TestMCPConfig:
"""Test MCPConfig functionality."""
async def test_run_mcp_config(self, tmp_path: Path):
"""Test creating a server from an MCPConfig file."""
server_script = inspect.cleandoc("""
from fastmcp import FastMCP
mcp = FastMCP()
@mcp.tool
def add(a: int, b: int) -> int:
return a + b
if __name__ == '__main__':
mcp.run()
""")
script_path: Path = tmp_path / "test.py"
script_path.write_text(server_script)
mcp_config_path = tmp_path / "mcp_config.json"
mcp_config = MCPConfig(
mcpServers={
"test_server": StdioMCPServer(command="python", args=[str(script_path)])
}
)
mcp_config.write_to_file(mcp_config_path)
server: FastMCP[None] = create_mcp_config_server(mcp_config_path)
client = Client[FastMCPTransport](server)
async with client:
tools = await client.list_tools()
assert len(tools) == 1
async def test_validate_mcp_config(self, tmp_path: Path):
"""Test creating a server from an MCPConfig file."""
mcp_config_path = tmp_path / "mcp_config.json"
mcp_config = {"mcpServers": {"test_server": dict(x=1, y=2)}}
with mcp_config_path.open("w") as f:
json.dump(mcp_config, f)
with pytest.raises(ValidationError, match="validation errors for MCPConfig"):
create_mcp_config_server(mcp_config_path)
class TestServerImport:
"""Test server import functionality using real files."""
async def test_import_server_basic_mcp(self, tmp_path):
"""Test importing server with basic FastMCP server."""
test_file = tmp_path / "server.py"
test_file.write_text("""
import fastmcp
mcp = fastmcp.FastMCP("TestServer")
@mcp.tool
def greet(name: str) -> str:
return f"Hello, {name}!"
""")
server = await import_server(test_file)
assert server.name == "TestServer"
tools = await server.get_tools()
assert "greet" in tools
async def test_import_server_with_main_block(self, tmp_path):
"""Test importing server with if __name__ == '__main__' block."""
test_file = tmp_path / "server.py"
test_file.write_text("""
import fastmcp
app = fastmcp.FastMCP("MainServer")
@app.tool
def calculate(x: int, y: int) -> int:
return x + y
if __name__ == "__main__":
app.run()
""")
server = await import_server(test_file)
assert server.name == "MainServer"
tools = await server.get_tools()
assert "calculate" in tools
async def test_import_server_standard_names(self, tmp_path):
"""Test automatic detection of standard names (mcp, server, app)."""
# Test with 'mcp' name
mcp_file = tmp_path / "mcp_server.py"
mcp_file.write_text("""
import fastmcp
mcp = fastmcp.FastMCP("MCPServer")
""")
server = await import_server(mcp_file)
assert server.name == "MCPServer"
# Test with 'server' name
server_file = tmp_path / "server_server.py"
server_file.write_text("""
import fastmcp
server = fastmcp.FastMCP("ServerServer")
""")
server = await import_server(server_file)
assert server.name == "ServerServer"
# Test with 'app' name
app_file = tmp_path / "app_server.py"
app_file.write_text("""
import fastmcp
app = fastmcp.FastMCP("AppServer")
""")
server = await import_server(app_file)
assert server.name == "AppServer"
async def test_import_server_nonstandard_name(self, tmp_path):
"""Test importing server with non-standard object name."""
test_file = tmp_path / "server.py"
test_file.write_text("""
import fastmcp
my_custom_server = fastmcp.FastMCP("CustomServer")
@my_custom_server.tool
def custom_tool() -> str:
return "custom"
""")
server = await import_server(test_file, "my_custom_server")
assert server.name == "CustomServer"
tools = await server.get_tools()
assert "custom_tool" in tools
async def test_import_server_no_standard_names_fails(self, tmp_path):
"""Test importing server when no standard names exist fails."""
test_file = tmp_path / "server.py"
test_file.write_text("""
import fastmcp
other_name = fastmcp.FastMCP("OtherServer")
""")
with pytest.raises(SystemExit) as exc_info:
await import_server(test_file)
assert exc_info.value.code == 1
async def test_import_server_nonexistent_object_fails(self, tmp_path):
"""Test importing nonexistent server object fails."""
test_file = tmp_path / "server.py"
test_file.write_text("""
import fastmcp
mcp = fastmcp.FastMCP("TestServer")
""")
with pytest.raises(SystemExit) as exc_info:
await import_server(test_file, "nonexistent")
assert exc_info.value.code == 1
|