diff --git a/gns3server/api/routes/mcp/__init__.py b/gns3server/api/routes/mcp/__init__.py index 0c6b037b0..c08c4507a 100644 --- a/gns3server/api/routes/mcp/__init__.py +++ b/gns3server/api/routes/mcp/__init__.py @@ -512,10 +512,16 @@ async def node_create( @mcp.tool() async def node_delete( project_id: Annotated[str, Field(description="UUID of the project")], - node_id: Annotated[str, Field(description="UUID of the node to delete")], + node_id: Annotated[str | None, Field(description="Node UUID (single mode)")] = None, + node_ids: Annotated[list[str] | None, Field(description="Batch mode: [\"uuid1\",\"uuid2\"] — delete multiple nodes in parallel")] = None, ) -> list[dict[str, Any]]: - """Delete a node from a project.""" - return await asyncio.to_thread(_run_handler_sync, delete_node_handler, {"project_id": project_id, "node_id": node_id}) + """Delete one or more nodes from a project. Provide node_id for single, or node_ids for batch.""" + params = {"project_id": project_id} + if node_ids: + params["node_ids"] = node_ids + else: + params["node_id"] = node_id + return await asyncio.to_thread(_run_handler_sync, delete_node_handler, params) @mcp.tool() diff --git a/gns3server/api/routes/mcp/nodes.py b/gns3server/api/routes/mcp/nodes.py index 5eb962e97..f3489da76 100644 --- a/gns3server/api/routes/mcp/nodes.py +++ b/gns3server/api/routes/mcp/nodes.py @@ -244,9 +244,24 @@ def create_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dic def delete_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]: project_id = params.get("project_id") + if not project_id: + return {"error": "project_id is required"} + node_ids = params.get("node_ids") + if node_ids: + if not isinstance(node_ids, list): + return {"error": "node_ids must be a list"} + conn = _get_connector(gns3_ctx) + def _del(nid): + try: + conn.http_call("delete", f"{conn.base_url}/projects/{project_id}/nodes/{nid}") + return {"node_id": nid, "status": "deleted"} + except Exception as e: + return {"node_id": nid, "status": "error", "error": str(e)} + with ThreadPoolExecutor(max_workers=min(len(node_ids), BATCH_MAX_WORKERS)) as pool: + return list(pool.map(_del, node_ids)) node_id = params.get("node_id") - if not project_id or not node_id: - return {"error": "project_id and node_id are required"} + if not node_id: + return {"error": "node_id or node_ids is required"} conn = _get_connector(gns3_ctx) conn.http_call("delete", f"{conn.base_url}/projects/{project_id}/nodes/{node_id}") return {"message": f"Node {node_id} deleted", "node_id": node_id}