mirror of
https://github.com/mcp-use/mcp-use.git
synced 2026-09-14 18:06:01 +08:00
e935514fc7
* fix(python/server): simplify tool JSON Schema for Optional parameters
Pydantic generates {"anyOf": [{"type": "T"}, {"type": "null"}]} for
Optional[T] tool parameters. While valid JSON Schema 2020-12, this
pattern breaks description rendering in the MCP Inspector UI and fails
Gemini API validation (requires top-level type field). MCP signals
optionality via the required array, making the anyOf/null wrapper
redundant.
- Add simplify_optional_schema() utility that collapses nullable anyOf
into flat {"type": "T"} while preserving description, default, title
- Override MCPServer.add_tool() to apply simplification after Pydantic
generates the schema
- Add 12 unit tests for the schema simplifier (test_json_schema.py)
- Add 23 unit tests for tool schema output (test_list_tool_format.py)
- Update server_example.py with representative parameter patterns
including Literal enum types
* docs(python/server): add parameter types and JSON Schema section to tools docs
Document the Python-to-JSON-Schema type mapping, required/optional/nullable
parameter patterns, and Literal enum usage. Includes a note explaining the
anyOf/null simplification that mcp-use applies automatically.
* feat(python/server): add icons parameter to MCPServer constructor
Pass through the icons parameter to FastMCP so server-level icons can
be set directly in the MCPServer constructor.
* docs(python/server): document icons parameter in server overview
* docs(python): update server example with icons and Literal enum params
* style: format server_example.py
* test(python): add unit tests for icons constructor parameter
* docs: regenerate API reference for icons param
153 lines
5.8 KiB
Python
153 lines
5.8 KiB
Python
"""
|
|
Unit tests for MCPServer configuration.
|
|
|
|
These tests verify that:
|
|
1. Default host/port values are cloud-friendly (0.0.0.0:8000)
|
|
2. Custom host/port can be set at initialization
|
|
3. run() can override host/port from __init__
|
|
4. Settings are correctly passed to FastMCP
|
|
5. Debug routes are registered correctly
|
|
6. Icons parameter is passed through to FastMCP
|
|
"""
|
|
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
from mcp.types import Icon
|
|
|
|
from mcp_use.server import MCPServer
|
|
|
|
|
|
class TestMCPServerDefaults:
|
|
"""Test default configuration values."""
|
|
|
|
def test_default_host_is_cloud_friendly(self):
|
|
"""Default host should be 0.0.0.0 for cloud/proxy compatibility."""
|
|
server = MCPServer(name="test-server")
|
|
assert server.settings.host == "0.0.0.0"
|
|
|
|
def test_default_port_is_8000(self):
|
|
"""Default port should be 8000."""
|
|
server = MCPServer(name="test-server")
|
|
assert server.settings.port == 8000
|
|
|
|
|
|
class TestMCPServerCustomConfig:
|
|
"""Test custom host/port configuration at init."""
|
|
|
|
def test_custom_host_at_init(self):
|
|
"""Host can be customized at initialization."""
|
|
server = MCPServer(name="test-server", host="127.0.0.1")
|
|
assert server.settings.host == "127.0.0.1"
|
|
|
|
def test_custom_port_at_init(self):
|
|
"""Port can be customized at initialization."""
|
|
server = MCPServer(name="test-server", port=3000)
|
|
assert server.settings.port == 3000
|
|
|
|
def test_custom_host_and_port_at_init(self):
|
|
"""Both host and port can be customized together."""
|
|
server = MCPServer(name="test-server", host="127.0.0.1", port=9000)
|
|
assert server.settings.host == "127.0.0.1"
|
|
assert server.settings.port == 9000
|
|
|
|
|
|
class TestDNSRebindingProtection:
|
|
"""Test DNS rebinding protection based on host."""
|
|
|
|
def test_localhost_enables_dns_protection(self):
|
|
"""127.0.0.1 should auto-enable DNS rebinding protection."""
|
|
server = MCPServer(name="test-server", host="127.0.0.1")
|
|
security = server.settings.transport_security
|
|
assert security is not None
|
|
assert security.enable_dns_rebinding_protection is True
|
|
|
|
def test_all_interfaces_disables_dns_protection(self):
|
|
"""0.0.0.0 should not enable DNS rebinding protection."""
|
|
server = MCPServer(name="test-server", host="0.0.0.0")
|
|
security = server.settings.transport_security
|
|
# Either None or explicitly disabled
|
|
assert security is None or security.enable_dns_rebinding_protection is False
|
|
|
|
|
|
class TestRunHostOverride:
|
|
"""Test that run() properly reconfigures DNS protection when host changes."""
|
|
|
|
def test_dns_rebinding_protection_flag_enables_protection(self):
|
|
"""Setting dns_rebinding_protection=True should enable DNS protection."""
|
|
server = MCPServer(name="test-server", dns_rebinding_protection=True)
|
|
assert server.settings.transport_security is not None
|
|
assert server.settings.transport_security.enable_dns_rebinding_protection is True
|
|
|
|
def test_dns_rebinding_protection_default_disabled(self):
|
|
"""DNS protection should be disabled by default."""
|
|
server = MCPServer(name="test-server")
|
|
assert (
|
|
server.settings.transport_security is None
|
|
or server.settings.transport_security.enable_dns_rebinding_protection is False
|
|
)
|
|
|
|
|
|
class TestDebugRoutes:
|
|
"""Test that debug routes are registered correctly."""
|
|
|
|
def _get_route_paths(self, server: MCPServer) -> list[str]:
|
|
return [r.path for r in server._custom_starlette_routes]
|
|
|
|
def test_debug_false_no_dev_routes(self):
|
|
"""debug=False should not register dev routes."""
|
|
server = MCPServer(name="test-server", debug=False)
|
|
paths = self._get_route_paths(server)
|
|
assert "/docs" not in paths
|
|
assert "/inspector" not in paths
|
|
assert "/openmcp.json" not in paths
|
|
|
|
def test_debug_true_at_init_registers_routes(self):
|
|
"""debug=True at init should register dev routes."""
|
|
server = MCPServer(name="test-server", debug=True)
|
|
paths = self._get_route_paths(server)
|
|
assert "/docs" in paths
|
|
assert "/inspector" in paths
|
|
assert "/openmcp.json" in paths
|
|
|
|
def test_debug_true_at_run_registers_routes(self):
|
|
"""debug=True passed to run() should register dev routes even if init had debug=False.
|
|
|
|
Regression test for https://github.com/mcp-use/mcp-use/issues/1099
|
|
"""
|
|
server = MCPServer(name="test-server", debug=False)
|
|
assert "/docs" not in self._get_route_paths(server)
|
|
|
|
# Patch ServerRunner.run to prevent actually starting the server
|
|
with patch("mcp_use.server.server.ServerRunner.run"):
|
|
server.run(debug=True)
|
|
|
|
paths = self._get_route_paths(server)
|
|
assert "/docs" in paths
|
|
assert "/inspector" in paths
|
|
assert "/openmcp.json" in paths
|
|
assert server.debug_level == 1
|
|
|
|
|
|
class TestServerIcons:
|
|
"""Test that the icons parameter propagates to FastMCP."""
|
|
|
|
def test_no_icons_by_default(self):
|
|
server = MCPServer(name="test-server")
|
|
assert server._mcp_server.icons is None
|
|
|
|
def test_icons_passed_through(self):
|
|
icons = [Icon(src="https://example.com/icon.png", mimeType="image/png")]
|
|
server = MCPServer(name="test-server", icons=icons)
|
|
assert server._mcp_server.icons == icons
|
|
|
|
def test_multiple_icons(self):
|
|
icons = [
|
|
Icon(src="https://example.com/light.svg", mimeType="image/svg+xml"),
|
|
Icon(src="https://example.com/dark.png", mimeType="image/png"),
|
|
]
|
|
server = MCPServer(name="test-server", icons=icons)
|
|
assert len(server._mcp_server.icons) == 2
|
|
assert server._mcp_server.icons[0].src == "https://example.com/light.svg"
|
|
assert server._mcp_server.icons[1].src == "https://example.com/dark.png"
|