From 80e64ebbae868980a492fb175463bd1de067d81e Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Wed, 10 Jun 2026 23:01:27 +0800 Subject: [PATCH] fix: Fix symbol_get/upload/delete handlers for correct API paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - get_symbol: URL was missing '/raw' suffix + tried .json() on binary SVG → now returns download URL + curl command (like download_capture_file) - upload_symbol: URL was missing '/raw' suffix → now accepts SVG content string and POSTs to correct path - delete_symbol: returns 204 No Content, .json() would fail → removed .json() call (just returns message) --- gns3server/api/routes/mcp/__init__.py | 7 ++++--- gns3server/api/routes/mcp/symbols.py | 20 ++++++++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/gns3server/api/routes/mcp/__init__.py b/gns3server/api/routes/mcp/__init__.py index 9f873352a..1523be18e 100644 --- a/gns3server/api/routes/mcp/__init__.py +++ b/gns3server/api/routes/mcp/__init__.py @@ -1037,7 +1037,7 @@ async def symbol_list() -> list[dict[str, Any]]: async def symbol_get( symbol_id: Annotated[str, Field(description="Symbol ID (e.g. ':/symbols/router.svg')")], ) -> list[dict[str, Any]]: - """Get details about a specific symbol.""" + """Get a download URL for a symbol file (SVG). Use curl to download.""" return await asyncio.to_thread(_run_handler_sync, get_symbol_handler, { "symbol_id": symbol_id, }) @@ -1062,10 +1062,11 @@ async def symbol_defaults() -> list[dict[str, Any]]: @mcp.tool() async def symbol_upload( symbol_id: Annotated[str, Field(description="Symbol ID to upload (e.g. ':/symbols/my_symbol.svg')")], + content: Annotated[str, Field(description="SVG content of the symbol")], ) -> list[dict[str, Any]]: - """Upload or update a custom symbol on the server.""" + """Upload or update a custom symbol on the server. Provide the SVG content as a string.""" return await asyncio.to_thread(_run_handler_sync, upload_symbol_handler, { - "symbol_id": symbol_id, + "symbol_id": symbol_id, "content": content, }) diff --git a/gns3server/api/routes/mcp/symbols.py b/gns3server/api/routes/mcp/symbols.py index 5ff2d56d8..a49e417d5 100644 --- a/gns3server/api/routes/mcp/symbols.py +++ b/gns3server/api/routes/mcp/symbols.py @@ -50,8 +50,14 @@ def get_symbol_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict symbol_id = params.get("symbol_id") if not symbol_id: return {"error": "symbol_id is required"} - conn = _get_connector(gns3_ctx) - return conn.http_call("get", f"{conn.base_url}/symbols/{symbol_id}").json() + download_url = f"{gns3_ctx['server_url']}/v3/symbols/{symbol_id}/raw" + auth_token = gns3_ctx['jwt_token'] + return { + "symbol_id": symbol_id, + "download_url": download_url, + "curl_command": f"curl -L -o '{symbol_id.replace(':', '').replace('/', '_')}.svg' -H 'Authorization: Bearer {auth_token}' '{download_url}'", + "note": "Symbol files are SVG images. Use curl to download.", + } def get_symbol_dimensions_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]: @@ -70,11 +76,13 @@ def get_default_symbols_handler(params: dict[str, Any], gns3_ctx: dict[str, Any] def upload_symbol_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]: symbol_id = params.get("symbol_id") - if not symbol_id: - return {"error": "symbol_id is required"} + content = params.get("content") + if not symbol_id or content is None: + return {"error": "symbol_id and content (SVG data) are required"} conn = _get_connector(gns3_ctx) - result = conn.http_call("post", f"{conn.base_url}/symbols/{symbol_id}").json() - return {"message": f"Symbol {symbol_id} uploaded", "symbol": result} + url = f"{conn.base_url}/symbols/{symbol_id}/raw" + conn.http_call("post", url, data=content, headers={"Content-Type": "image/svg+xml"}) + return {"message": f"Symbol {symbol_id} uploaded", "symbol_id": symbol_id} def delete_symbol_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]: