From 58ca6b1dd44e478085d4424344aa741a6db00200 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Mon, 15 Jun 2026 22:11:14 +0800 Subject: [PATCH] Add fields filter to template_list tool with description for AI --- gns3server/api/routes/mcp/__init__.py | 8 +++++-- gns3server/api/routes/mcp/templates.py | 29 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/gns3server/api/routes/mcp/__init__.py b/gns3server/api/routes/mcp/__init__.py index 23b14e145..f81130611 100644 --- a/gns3server/api/routes/mcp/__init__.py +++ b/gns3server/api/routes/mcp/__init__.py @@ -665,9 +665,13 @@ async def link_update( # ── Template tools ──────────────────────────────────────────────────── @mcp.tool() -async def template_list() -> list[dict[str, Any]]: +async def template_list( + fields: Annotated[list[str] | None, Field(description="Response fields to include (default: [template_id, name, template_type, category, default_name_format]). " + "Available: template_id, name, version, category, default_name_format, symbol, " + "template_type, compute_id, usage, tags, builtin, created_at, updated_at")] = None, +) -> dict[str, Any]: """List all available templates on the server.""" - return await asyncio.to_thread(_run_handler_sync, list_templates_handler, {}) + return await asyncio.to_thread(_run_handler_sync, list_templates_handler, {"fields": fields}) @mcp.tool() diff --git a/gns3server/api/routes/mcp/templates.py b/gns3server/api/routes/mcp/templates.py index 01006ee94..af8eeaf40 100644 --- a/gns3server/api/routes/mcp/templates.py +++ b/gns3server/api/routes/mcp/templates.py @@ -43,9 +43,38 @@ def _get_connector(gns3_ctx: dict[str, Any]): # ── Tool handlers ────────────────────────────────────────────────────────── +VALID_TEMPLATE_FIELDS = { + "template_id", "name", "version", "category", "default_name_format", + "symbol", "template_type", "compute_id", "usage", "tags", "builtin", + "created_at", "updated_at", +} + +TEMPLATE_DEFAULT_FIELDS = ["template_id", "name", "template_type", "category", "default_name_format"] + + +def _filter_templates(templates, fields): + """Filter each template to only include requested fields.""" + if not fields: + fields = TEMPLATE_DEFAULT_FIELDS + if isinstance(templates, dict): + return {k: templates[k] for k in fields if k in templates} + return [{k: t[k] for k in fields if k in t} for t in templates] + + def list_templates_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]: conn = _get_connector(gns3_ctx) templates = conn.http_call("get", f"{conn.base_url}/templates").json() + fields = params.get("fields") + if fields: + if not isinstance(fields, list): + return {"error": "fields must be a list, e.g. [\"template_id\", \"name\"]"} + invalid = [f for f in fields if f not in VALID_TEMPLATE_FIELDS] + if invalid: + return { + "error": f"Unknown fields: {invalid}", + "available_fields": sorted(VALID_TEMPLATE_FIELDS), + } + templates = _filter_templates(templates, fields) return {"templates": templates, "count": len(templates)}