fix: attach MCP-created nodes/edges to a design (#225)

create_node/create_edge persisted rows with design_id=null when the
client omitted it (the MCP write tools), so they existed in the DB but
never rendered on the canvas until a container restart reconciled them.
Both routes now fall back to the first design, matching bulk-approve.

Also fix MCP resource reads (homelable://canvas, homelable://edges):
the framework passes a pydantic AnyUrl, not a str, which raised
"'AnyUrl' object has no attribute 'startswith'". Coerce to str.

EdgeResponse now exposes design_id for symmetry with NodeResponse.

ha-relevant: no
This commit is contained in:
Pouzor
2026-06-28 14:00:59 +02:00
parent 7e99d77edc
commit cbc2bc03c2
7 changed files with 121 additions and 4 deletions
+31
View File
@@ -97,6 +97,37 @@ async def test_create_edge_requires_auth(client: AsyncClient, two_nodes):
assert res.status_code == 401
async def test_create_edge_without_design_id_falls_back_to_first_design(client: AsyncClient, headers: dict, two_nodes):
# Regression for #225: MCP create_edge sent no design_id, so edges were
# persisted with design_id=null and never rendered until a restart.
src, tgt = two_nodes
design = await client.post("/api/v1/designs", json={"name": "Primary"}, headers=headers)
design_id = design.json()["id"]
res = await client.post(
"/api/v1/edges",
json={"source": src, "target": tgt, "type": "ethernet"},
headers=headers,
)
assert res.status_code == 201
assert res.json()["design_id"] == design_id
async def test_create_edge_respects_explicit_design_id(client: AsyncClient, headers: dict, two_nodes):
src, tgt = two_nodes
await client.post("/api/v1/designs", json={"name": "First"}, headers=headers)
second = await client.post("/api/v1/designs", json={"name": "Second"}, headers=headers)
second_id = second.json()["id"]
res = await client.post(
"/api/v1/edges",
json={"source": src, "target": tgt, "type": "ethernet", "design_id": second_id},
headers=headers,
)
assert res.status_code == 201
assert res.json()["design_id"] == second_id
async def test_create_cluster_edge_with_handles(client: AsyncClient, headers: dict, two_nodes):
src, tgt = two_nodes
res = await client.post(