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:
@@ -4,7 +4,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.deps import get_current_user
|
||||
from app.db.database import get_db
|
||||
from app.db.models import Edge
|
||||
from app.db.models import Design, Edge
|
||||
from app.schemas.edges import EdgeCreate, EdgeResponse, EdgeUpdate
|
||||
|
||||
router = APIRouter()
|
||||
@@ -18,7 +18,14 @@ async def list_edges(db: AsyncSession = Depends(get_db), _: str = Depends(get_cu
|
||||
|
||||
@router.post("", response_model=EdgeResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def create_edge(body: EdgeCreate, db: AsyncSession = Depends(get_db), _: str = Depends(get_current_user)) -> Edge:
|
||||
edge = Edge(**body.model_dump())
|
||||
data = body.model_dump()
|
||||
# Same reconciliation as nodes: clients omitting design_id (MCP write tools)
|
||||
# would create design_id=null edges that never render until a restart.
|
||||
# Fall back to the first design so the edge attaches to a canvas.
|
||||
if data.get("design_id") is None:
|
||||
first_design = (await db.execute(select(Design).order_by(Design.created_at).limit(1))).scalar()
|
||||
data["design_id"] = first_design.id if first_design else None
|
||||
edge = Edge(**data)
|
||||
db.add(edge)
|
||||
await db.commit()
|
||||
await db.refresh(edge)
|
||||
|
||||
@@ -4,7 +4,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.deps import get_current_user
|
||||
from app.db.database import get_db
|
||||
from app.db.models import Node
|
||||
from app.db.models import Design, Node
|
||||
from app.schemas.nodes import NodeCreate, NodeResponse, NodeUpdate
|
||||
|
||||
router = APIRouter()
|
||||
@@ -18,7 +18,15 @@ async def list_nodes(db: AsyncSession = Depends(get_db), _: str = Depends(get_cu
|
||||
|
||||
@router.post("", response_model=NodeResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def create_node(body: NodeCreate, db: AsyncSession = Depends(get_db), _: str = Depends(get_current_user)) -> Node:
|
||||
node = Node(**body.model_dump())
|
||||
data = body.model_dump()
|
||||
# Attach to a design so the node lands on a canvas. Clients that don't send a
|
||||
# design_id (e.g. the MCP write tools) would otherwise create design_id=null
|
||||
# nodes that exist in the DB but never render in the UI until a container
|
||||
# restart reconciles them. Fall back to the first design, matching bulk-approve.
|
||||
if data.get("design_id") is None:
|
||||
first_design = (await db.execute(select(Design).order_by(Design.created_at).limit(1))).scalar()
|
||||
data["design_id"] = first_design.id if first_design else None
|
||||
node = Node(**data)
|
||||
db.add(node)
|
||||
await db.commit()
|
||||
await db.refresh(node)
|
||||
|
||||
Reference in New Issue
Block a user