7935b671d3
Exposes homelab topology to MCP-compatible AI clients (Claude Code, etc.) over LAN via HTTP/SSE on port 8001. - New mcp/ service: FastAPI + mcp SDK, SSE transport - Auth: X-API-Key for AI clients, X-MCP-Service-Key for backend (Docker-internal) - Resources: canvas, nodes, edges, scan/pending, scan/runs - Tools: create/update/delete nodes+edges, trigger scan, approve/hide devices - Backend deps.py: accepts JWT or MCP service key (no plain-text password) - 40 tests (auth, resources, tools) across asyncio + trio - docker-compose.yml: mcp service on port 8001 - README: MCP setup section with Claude Code/Desktop config examples
48 lines
1.3 KiB
Python
48 lines
1.3 KiB
Python
import pytest
|
|
from unittest.mock import AsyncMock, patch
|
|
from app.resources import read_resource
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_backend():
|
|
with patch("app.resources.backend") as m:
|
|
m.get = AsyncMock(return_value={"data": "ok"})
|
|
yield m
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_read_canvas(mock_backend):
|
|
result = await read_resource("homelable://canvas")
|
|
mock_backend.get.assert_called_once_with("/api/v1/canvas")
|
|
assert len(result) == 1
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_read_nodes(mock_backend):
|
|
await read_resource("homelable://nodes")
|
|
mock_backend.get.assert_called_once_with("/api/v1/nodes")
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_read_edges(mock_backend):
|
|
await read_resource("homelable://edges")
|
|
mock_backend.get.assert_called_once_with("/api/v1/edges")
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_read_single_node(mock_backend):
|
|
await read_resource("homelable://nodes/abc123")
|
|
mock_backend.get.assert_called_once_with("/api/v1/nodes/abc123")
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_read_scan_pending(mock_backend):
|
|
await read_resource("homelable://scan/pending")
|
|
mock_backend.get.assert_called_once_with("/api/v1/scan/pending")
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_read_unknown_uri(mock_backend):
|
|
with pytest.raises(ValueError, match="Unknown resource URI"):
|
|
await read_resource("homelable://unknown")
|