Add compact array format for link node entries to reduce token usage

Supports both standard and compact formats:
  Standard: [{"node_id": "uuid", "adapter_number": 0, "port_number": 0}]
  Compact:  ["uuid", 0, 0, "uuid", 0, 0] - 3x less tokens
This commit is contained in:
YueGuobin 2026-06-15 21:42:26 +08:00
parent 27a489a3fd
commit a8a83051ff
No known key found for this signature in database
2 changed files with 26 additions and 5 deletions

View File

@ -596,10 +596,10 @@ async def link_get(
@mcp.tool()
async def link_create(
project_id: Annotated[str, Field(description="UUID of the project")],
nodes: Annotated[list | None, Field(description="Single mode: [{node_id, adapter_number, port_number}]")] = None,
nodes: Annotated[list | None, Field(description="Single mode: [{node_id, adapter_number, port_number}] or compact [id, ad, pt, id, ad, pt]")] = None,
link_type: Annotated[str, Field(description="Link type - ethernet or serial")] = "ethernet",
filters: Annotated[dict | None, Field(description="Optional packet filters")] = None,
links: Annotated[list | None, Field(description="Batch mode: [{nodes, link_type?, filters?}] — creates multiple links in parallel")] = None,
links: Annotated[list | None, Field(description="Batch mode: [{nodes, link_type?, filters?}] — nodes supports compact [id, ad, pt, id, ad, pt] format")] = None,
fields: Annotated[list[str] | None, Field(description="Response fields to include (default: [link_id, link_type, nodes]). "
"Available: link_id, project_id, link_type, nodes, suspend, "
"link_style, filters, show_filters_icon, capturing, "

View File

@ -46,6 +46,26 @@ def _get_connector(gns3_ctx: dict[str, Any]):
)
def _normalize_link_nodes(nodes) -> list[dict[str, Any]]:
"""
Normalize link node entries, accepting both standard object format and
compact array format to reduce token usage.
Standard: [{"node_id": "uuid", "adapter_number": 0, "port_number": 0}]
Compact: ["uuid", 0, 0, "uuid", 0, 0]
"""
if not nodes:
return nodes
if isinstance(nodes[0], dict):
return nodes
if isinstance(nodes, list) and len(nodes) == 6:
return [
{"node_id": nodes[0], "adapter_number": nodes[1], "port_number": nodes[2]},
{"node_id": nodes[3], "adapter_number": nodes[4], "port_number": nodes[5]},
]
return nodes
# ── Tool handlers ──────────────────────────────────────────────────────────
VALID_LINK_FIELDS = {
@ -100,10 +120,11 @@ def create_link_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dic
results = []
conn = _get_connector(gns3_ctx)
def _create_one(link_data):
if not link_data.get("nodes"):
raw_nodes = link_data.get("nodes")
if not raw_nodes:
return {"status": "error", "error": "nodes is required for each link"}
try:
body = {"nodes": link_data["nodes"]}
body = {"nodes": _normalize_link_nodes(raw_nodes)}
if link_data.get("link_type"):
body["link_type"] = link_data["link_type"]
if link_data.get("filters"):
@ -126,7 +147,7 @@ def create_link_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dic
if not nodes:
return {"error": "nodes is required"}
conn = _get_connector(gns3_ctx)
data = {"nodes": nodes}
data = {"nodes": _normalize_link_nodes(nodes)}
if "link_type" in params:
data["link_type"] = params["link_type"]
if "filters" in params: