From 3d3c0eb8db4a564b5b39f2070f57dad22cd296fb Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Sat, 13 Jun 2026 23:59:12 +0800 Subject: [PATCH] feat: Add fields filter to appliance_list Appliances list can be very large (hundreds of entries). Use fields=["name","category"] to return only what the AI needs. --- gns3server/api/routes/mcp/__init__.py | 8 +++++--- gns3server/api/routes/mcp/appliances.py | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/gns3server/api/routes/mcp/__init__.py b/gns3server/api/routes/mcp/__init__.py index d6036ecc9..0b8abad61 100644 --- a/gns3server/api/routes/mcp/__init__.py +++ b/gns3server/api/routes/mcp/__init__.py @@ -1171,9 +1171,11 @@ async def symbol_delete( @mcp.tool() -async def appliance_list() -> list[dict[str, Any]]: - """List all available appliances (template library).""" - return await asyncio.to_thread(_run_handler_sync, get_appliances_handler, {}) +async def appliance_list( + fields: Annotated[list[str] | None, Field(description="Optional: return only these fields. e.g. [\"name\",\"category\"]. Available: name, category, description, vendor_name, product_name, status, availability, images, versions, tags, symbol, usage, builtin")] = None, +) -> list[dict[str, Any]]: + """List all available appliances (template library). Use fields=[] to return only what you need.""" + return await asyncio.to_thread(_run_handler_sync, get_appliances_handler, {"fields": fields} if fields else {}) @mcp.tool() diff --git a/gns3server/api/routes/mcp/appliances.py b/gns3server/api/routes/mcp/appliances.py index 426538560..a44cdb852 100644 --- a/gns3server/api/routes/mcp/appliances.py +++ b/gns3server/api/routes/mcp/appliances.py @@ -40,9 +40,30 @@ def _get_connector(gns3_ctx: dict[str, Any]): # ── Tool handlers ────────────────────────────────────────────────────────── +VALID_APPLIANCE_FIELDS = { + "appliance_id", "name", "category", "description", "vendor_name", + "vendor_url", "product_name", "product_url", "documentation_url", + "status", "availability", "maintainer", "usage", "symbol", + "images", "versions", "tags", "builtin", + "first_port_name", "port_name_format", "port_segment_size", + "linked_clone", "docker", "iou", "dynamips", "qemu", +} + + def get_appliances_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]: conn = _get_connector(gns3_ctx) appliances = conn.http_call("get", f"{conn.base_url}/appliances").json() + fields = params.get("fields") + if fields: + if not isinstance(fields, list): + return {"error": "fields must be a list, e.g. [\"name\", \"category\"]"} + invalid = [f for f in fields if f not in VALID_APPLIANCE_FIELDS] + if invalid: + return { + "error": f"Unknown fields: {invalid}", + "available_fields": sorted(VALID_APPLIANCE_FIELDS), + } + appliances = [{k: a[k] for k in fields if k in a} for a in appliances] return {"appliances": appliances, "count": len(appliances)}