"""Tests for MCP Apps Phase 1 — ui:// resource and AppConfig metadata."""
import pytest
from fastmcp import Client, FastMCP
from fastmcp.server.apps import AppConfig
from tools.ui_apps import _build_manage_tools_html, setup_ui_apps
@pytest.fixture
def mcp_with_ui():
"""Create a minimal FastMCP server with UI apps registered."""
mcp = FastMCP("test-ui")
# Register a tool with ToolUI metadata (mirrors manage_tools)
@mcp.tool(
name="manage_tools",
app=AppConfig(
resource_uri="ui://manage-tools-dashboard",
visibility=["app", "model"],
),
)
def manage_tools_stub() -> str:
return "stub"
setup_ui_apps(mcp)
return mcp
# ── HTML builder tests ─────────────────────────────────────────────
def test_html_is_valid_document():
html = _build_manage_tools_html()
assert html.startswith("<!DOCTYPE html>")
assert "</html>" in html
assert "<head>" in html
assert "<body>" in html
def test_html_contains_groups_container():
html = _build_manage_tools_html()
assert 'id="groups-container"' in html
def test_html_contains_phase1_badge():
html = _build_manage_tools_html()
assert "Phase 1" in html
assert "Read-Only" in html
def test_html_contains_mcp_tools_placeholder():
html = _build_manage_tools_html()
assert "window.__MCP_TOOLS__" in html
# ── In-process client tests ────────────────────────────────────────
@pytest.mark.asyncio
async def test_tool_has_ui_meta(mcp_with_ui):
async with Client(mcp_with_ui) as client:
tools = await client.list_tools()
manage = next(t for t in tools if t.name == "manage_tools")
assert manage.meta is not None
ui = manage.meta.get("ui", {})
assert ui.get("resourceUri") == "ui://manage-tools-dashboard"
assert ui.get("visibility") == ["app", "model"]
@pytest.mark.asyncio
async def test_resource_exists(mcp_with_ui):
async with Client(mcp_with_ui) as client:
resources = await client.list_resources()
uris = [str(r.uri) for r in resources]
assert "ui://manage-tools-dashboard" in uris
@pytest.mark.asyncio
async def test_resource_serves_html(mcp_with_ui):
async with Client(mcp_with_ui) as client:
contents = await client.read_resource("ui://manage-tools-dashboard")
# read_resource returns a list of content items or a string
if isinstance(contents, list):
text = (
contents[0].text if hasattr(contents[0], "text") else str(contents[0])
)
else:
text = str(contents)
assert "<!DOCTYPE html>" in text
assert "tool-grid" in text
@pytest.mark.asyncio
async def test_resource_has_correct_mime(mcp_with_ui):
async with Client(mcp_with_ui) as client:
resources = await client.list_resources()
dashboard = next(
r for r in resources if str(r.uri) == "ui://manage-tools-dashboard"
)
# ui:// scheme should resolve to text/html;profile=mcp-app
assert dashboard.mimeType is not None
assert "text/html" in dashboard.mimeType
@pytest.mark.asyncio
async def test_resource_ui_meta(mcp_with_ui):
async with Client(mcp_with_ui) as client:
resources = await client.list_resources()
dashboard = next(
r for r in resources if str(r.uri) == "ui://manage-tools-dashboard"
)
assert dashboard.meta is not None
ui = dashboard.meta.get("ui", {})
assert ui.get("prefersBorder") is True