diff --git a/docs/features/mcp-service.md b/docs/features/mcp-service.md index 5390211a3..4963ad748 100644 --- a/docs/features/mcp-service.md +++ b/docs/features/mcp-service.md @@ -187,7 +187,9 @@ All subsequent tool handler REST API calls use this JWT → zero extra bcrypt |------|-------------| | `drawing_list` | List drawings on canvas | | `drawing_get` | Get drawing details | -| `drawing_create` | Create drawing (SVG label/shape/image) | +| `drawing_create_text` | Create a text label (SVG assembled server-side, Web UI compatible) | +| `drawing_create_rectangle` | Create a rectangle annotation | +| `drawing_create_ellipse` | Create an ellipse/circle annotation | | `drawing_update` | Update drawing (position, rotation, SVG) | | `drawing_delete` | Delete a drawing | diff --git a/gns3server/agent/mcp/__init__.py b/gns3server/agent/mcp/__init__.py index 96de2b828..57b45e5ea 100644 --- a/gns3server/agent/mcp/__init__.py +++ b/gns3server/agent/mcp/__init__.py @@ -112,7 +112,6 @@ from .device_config import ( vpcs_config_set_handler, ) from .drawings import ( - create_drawing_handler, delete_drawing_handler, get_drawing_handler, get_drawings_handler, @@ -1541,50 +1540,6 @@ async def drawing_list( ) -@mcp.tool() -async def drawing_create( - project_id: Annotated[str, Field(description="Project name or UUID")], - svg: Annotated[str, Field(description="SVG content for the drawing")], - x: Annotated[int, Field(description="X coordinate (default: 0)")] = 0, - y: Annotated[int, Field(description="Y coordinate (default: 0)")] = 0, - z: Annotated[int, Field(description="Z layer (default: 0)")] = 0, - locked: Annotated[bool, Field(description="Lock the drawing (default: false)")] = False, - rotation: Annotated[int, Field(description="Rotation angle in degrees, -359 to 359 (default: 0)")] = 0, -) -> list[dict[str, Any]]: - """Create a new drawing (label, shape, or image) on a project canvas from raw SVG. - - Web UI compatibility rules (it re-parses the SVG instead of rendering it raw): - - Root must be ; W/H set the selection box size and are required. - - Exactly ONE child element, from: text, image, rect, line, ellipse, path. - is not supported; extra children are dropped (first recognized child wins). - - is re-drawn as a freehand curve from the points in d — not a faithful render. - - : omit x/y (ignored, the rect sits at the drawing origin) and give a solid fill. - - : cx/cy are offsets inside the drawing box, not canvas coordinates. - - font-size is displayed in pt units by the Web UI (1pt = 4/3 px). - - For text labels use drawing_create_text instead — it handles escaping and sizing. - - SVG examples: - Rectangle: - Ellipse: - Line: - Dashed line: - """ - return await asyncio.to_thread( - _run_handler_sync, - create_drawing_handler, - { - "project_id": project_id, - "svg": svg, - "x": x, - "y": y, - "z": z, - "locked": locked, - "rotation": rotation, - }, - ) - - @mcp.tool() async def drawing_create_text( project_id: Annotated[str, Field(description="Project name or UUID")], @@ -1599,8 +1554,8 @@ async def drawing_create_text( ) -> list[dict[str, Any]]: """Add a text label to a project canvas. - The SVG is assembled server-side in the Web UI compatible format; prefer this - over hand-writing SVG for drawing_create. + The SVG is assembled server-side in the Web UI compatible format + (validation, escaping and sizing included). - XML characters (& < >) are escaped automatically. - Multi-line: embed newlines (\\n) in text; each line renders below the previous one. @@ -1647,8 +1602,8 @@ async def drawing_create_rectangle( ) -> list[dict[str, Any]]: """Add a rectangle to a project canvas. - The SVG is assembled server-side in the Web UI compatible format; prefer this - over hand-writing SVG for drawing_create. + The SVG is assembled server-side in the Web UI compatible format + (validation included). - fill "none" renders an outline-only box (stroke recommended then). - Border attributes are only emitted when stroke is given. diff --git a/gns3server/agent/mcp/drawings.py b/gns3server/agent/mcp/drawings.py index 7ca6daeda..7c459fe6a 100644 --- a/gns3server/agent/mcp/drawings.py +++ b/gns3server/agent/mcp/drawings.py @@ -51,24 +51,6 @@ def get_drawings_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> di return {"drawings": drawings, "count": len(drawings)} -def create_drawing_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]: - project_id = params.get("project_id") - svg = params.get("svg") - if not project_id or not svg: - return {"error": "project_id and svg are required"} - conn = _get_connector(gns3_ctx) - data = { - "svg": svg, - "x": params.get("x", 0), - "y": params.get("y", 0), - "z": params.get("z", 0), - "locked": params.get("locked", False), - "rotation": params.get("rotation", 0), - } - result = conn.http_call("post", f"{conn.base_url}/projects/{project_id}/drawings", json_data=data).json() - return {"message": "Drawing created", "drawing": result} - - def get_drawing_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]: project_id = params.get("project_id") drawing_id = params.get("drawing_id") diff --git a/tests/agent/mcp/test_tool_params.py b/tests/agent/mcp/test_tool_params.py index 050ba58ea..b4c01d910 100644 --- a/tests/agent/mcp/test_tool_params.py +++ b/tests/agent/mcp/test_tool_params.py @@ -83,7 +83,6 @@ HANDLER_FILES = { "delete_snapshot_handler": "snapshots.py", "restore_snapshot_handler": "snapshots.py", "get_drawings_handler": "drawings.py", - "create_drawing_handler": "drawings.py", "get_drawing_handler": "drawings.py", "update_drawing_handler": "drawings.py", "delete_drawing_handler": "drawings.py",