feat: expose data_link_type on the link_marker MCP tool (create-only)

Per-link markers on serial links need the WAN encapsulation (e.g.
DLT_C_HDLC) so the BPF compiles against the right link layer — the
REST API already accepts it, but the MCP tool never forwarded it.
Create passes it through; update ignores it (changing it would
invalidate the capture file), matching the REST schema semantics.
This commit is contained in:
YueGuobin 2026-08-25 13:29:39 +08:00
parent 9e4edc8a8a
commit f31bfffefc
No known key found for this signature in database
3 changed files with 30 additions and 2 deletions

View File

@ -900,7 +900,7 @@ def link_marker_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dic
if not bpf:
return {"error": "bpf is required for create action"}
body: dict[str, Any] = {"bpf": bpf}
for opt in ("name", "tag", "capture_node_id", "color", "highlight_duration"):
for opt in ("name", "tag", "capture_node_id", "color", "highlight_duration", "data_link_type"):
if params.get(opt) is not None:
body[opt] = params[opt]
# direction: "tx"/"rx" set a one-way filter; "both"/omitted = no filter.

View File

@ -1035,6 +1035,7 @@ async def link_marker(
capture_node_id: Annotated[str | None, Field(description="UUID of the endpoint whose uBridge hosts the marker (the observer; tx/rx are from its perspective). Must be a link endpoint and marker-capable. Omit to auto-pick.")] = None,
color: Annotated[str | None, Field(description="Hex color for UI highlight, e.g. '#ff5722'")] = None,
highlight_duration: Annotated[int | None, Field(description="UI highlight duration in milliseconds")] = None,
data_link_type: Annotated[str | None, Field(description="pcap link-layer type for serial links (create-only): DLT_C_HDLC / DLT_PPP_SERIAL / DLT_FRELAY / DLT_ATM_RFC1483, matching the encapsulation on the serial link. Omit = DLT_EN10MB (Ethernet). Ignored on update — changing it would invalidate the capture file.")] = None,
) -> list[dict[str, Any]]:
"""Manage traffic-insight markers on a link.
@ -1051,7 +1052,7 @@ async def link_marker(
and cannot be modified or deleted via this tool.
"""
params = {"project_id": project_id, "link_id": link_id, "action": action}
for opt in ("bpf", "marker_name", "name", "tag", "enabled", "direction", "capture_node_id", "color", "highlight_duration"):
for opt in ("bpf", "marker_name", "name", "tag", "enabled", "direction", "capture_node_id", "color", "highlight_duration", "data_link_type"):
val = locals().get(opt)
if val is not None:
params[opt] = val

View File

@ -437,6 +437,33 @@ class TestLinkMarker:
json_data={"bpf": "icmp"},
)
def test_create_data_link_type_passthrough(self, ctx):
"""create passes a serial WAN encapsulation through; update ignores it."""
from gns3server.agent.gns3_copilot.gns3_client.api_handlers import link_marker_handler
with patch(f"{AH}._get_connector") as m:
conn = _mock_conn({"name": "icmp"})
m.return_value = conn
link_marker_handler(
{"project_id": "p", "link_id": "l", "action": "create",
"bpf": "icmp", "data_link_type": "DLT_C_HDLC"}, ctx,
)
conn.http_call.assert_called_with(
"post", "http://192.168.1.3:3080/v3/projects/p/links/l/markers",
json_data={"bpf": "icmp", "data_link_type": "DLT_C_HDLC"},
)
conn = _mock_conn({"name": "icmp"})
m.return_value = conn
link_marker_handler(
{"project_id": "p", "link_id": "l", "action": "update",
"marker_name": "icmp", "tag": 1, "data_link_type": "DLT_PPP_SERIAL"}, ctx,
)
# create-only: dropped from the update body
conn.http_call.assert_called_with(
"put", "http://192.168.1.3:3080/v3/projects/p/links/l/markers/icmp",
json_data={"tag": 1},
)
def test_create_direction_tx(self, ctx):
from gns3server.agent.gns3_copilot.gns3_client.api_handlers import link_marker_handler
with patch(f"{AH}._get_connector") as m: