refactor(mcp): drop the raw drawing_create tool

The templated drawing_create_text/_rectangle/_ellipse tools cover the
LLM-relevant creation paths, and line/image/curve creation is not
needed from MCP or copilot for now. The management tools stay, so
existing drawings (including images and lines created in the Web UI)
remain fully manageable; drawing_update's raw svg parameter remains
the escape hatch for edits.
This commit is contained in:
YueGuobin 2026-10-08 21:12:12 +08:00
parent 5e8272fdd8
commit 4de6758e2f
No known key found for this signature in database
4 changed files with 7 additions and 69 deletions

View File

@ -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 |

View File

@ -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 <svg width="W" height="H">; W/H set the selection box size and are required.
- Exactly ONE child element, from: text, image, rect, line, ellipse, path.
<g> is not supported; extra children are dropped (first recognized child wins).
- <path> is re-drawn as a freehand curve from the points in d — not a faithful render.
- <rect>: omit x/y (ignored, the rect sits at the drawing origin) and give a solid fill.
- <ellipse>: 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: <svg width="80" height="50"><rect width="80" height="50" fill="#4A90D9"/></svg>
Ellipse: <svg width="80" height="40"><ellipse cx="40" cy="20" rx="40" ry="20" fill="red"/></svg>
Line: <svg width="100" height="100"><line x2="100" y2="100" stroke="black" stroke-width="2"/></svg>
Dashed line: <svg width="100" height="100"><line x2="100" y2="100" stroke="black" stroke-dasharray="5,5"/></svg>
"""
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.

View File

@ -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")

View File

@ -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",