From 8211816b37af415df3abbe5012113084c931c82e Mon Sep 17 00:00:00 2001 From: Adam Outler Date: Tue, 3 Feb 2026 16:51:31 +0100 Subject: [PATCH 1/4] feat(mcp): Expose OpenAPI spec as a resource (netalertx://api/openapi.json) --- server/api_server/mcp_endpoint.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/server/api_server/mcp_endpoint.py b/server/api_server/mcp_endpoint.py index db65ebe3..827338a2 100644 --- a/server/api_server/mcp_endpoint.py +++ b/server/api_server/mcp_endpoint.py @@ -795,8 +795,17 @@ def get_log_dir() -> str: def _list_resources() -> List[Dict[str, Any]]: - """List available MCP resources (read-only data like logs).""" + """List available MCP resources (read-only data like logs and API spec).""" resources = [] + + # API Specification + resources.append({ + "uri": "netalertx://api/openapi.json", + "name": "OpenAPI Specification", + "description": "The full OpenAPI 3.1 specification for the NetAlertX API and MCP tools", + "mimeType": "application/json" + }) + log_dir = get_log_dir() if not log_dir: return resources @@ -840,6 +849,16 @@ def _list_resources() -> List[Dict[str, Any]]: def _read_resource(uri: str) -> List[Dict[str, Any]]: """Read a resource by URI.""" + # Handle API Specification + if uri == "netalertx://api/openapi.json": + from flask import current_app + spec = get_openapi_spec(flask_app=current_app) + return [{ + "uri": uri, + "mimeType": "application/json", + "text": json.dumps(spec, indent=2) + }] + log_dir = get_log_dir() if not log_dir: return [{"uri": uri, "text": "Error: NETALERTX_LOG directory not configured"}] From a1a6c7e1cf97cd7f47d860397559fc024c269ecc Mon Sep 17 00:00:00 2001 From: Adam Outler Date: Tue, 3 Feb 2026 16:18:36 +0000 Subject: [PATCH 2/4] Skill: Agents can auto-configure MCP. --- .../skills/devcontainer-management/SKILL.md | 2 +- .gemini/skills/mcp-activation/SKILL.md | 52 +++++++++++++++++++ .github/skills/mcp-activation/SKILL.md | 34 ++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 .gemini/skills/mcp-activation/SKILL.md create mode 100644 .github/skills/mcp-activation/SKILL.md diff --git a/.gemini/skills/devcontainer-management/SKILL.md b/.gemini/skills/devcontainer-management/SKILL.md index 7d6694c6..5c3f54fb 100644 --- a/.gemini/skills/devcontainer-management/SKILL.md +++ b/.gemini/skills/devcontainer-management/SKILL.md @@ -1,6 +1,6 @@ --- name: devcontainer-management -description: Guide for identifying, managing, and running commands within the NetAlertX development container. Use this when asked to run backend logic, setup scripts, or troubleshoot container issues. +description: Guide for identifying, managing, and running commands within the NetAlertX development container. Use this when asked to run commands, testing, setup scripts, or troubleshoot container issues. --- # Devcontainer Management diff --git a/.gemini/skills/mcp-activation/SKILL.md b/.gemini/skills/mcp-activation/SKILL.md new file mode 100644 index 00000000..6938a7b9 --- /dev/null +++ b/.gemini/skills/mcp-activation/SKILL.md @@ -0,0 +1,52 @@ +--- +name: mcp-activation +description: Enables live interaction with the NetAlertX runtime. This skill configures the Model Context Protocol (MCP) connection, granting full API access for debugging, troubleshooting, and real-time operations including database queries, network scans, and device management. +--- + +# MCP Activation Skill + +This skill configures the NetAlertX development environment to expose the Model Context Protocol (MCP) server to AI agents. + +## Why use this? + +By default, agents only have access to the static codebase (files). To perform dynamic actions—such as: +- **Querying the database** (e.g., getting device lists, events) +- **Triggering actions** (e.g., network scans, Wake-on-LAN) +- **Validating runtime state** (e.g., checking if a fix actually works) + +...you need access to the **MCP Server** running inside the container. This skill sets up the necessary authentication tokens and connection configs to bridge your agent to that live server. + +## Prerequisites + +1. **Devcontainer:** You must be connected to the NetAlertX devcontainer. +2. **Server Running:** The backend server must be running (to generate `app.conf` with the API token). + +## Activation Steps + +1. **Activate Devcontainer Skill:** + If you are not already inside the container, activate the management skill: + ```text + activate_skill("devcontainer-management") + ``` + +2. **Generate Configurations:** + Run the configuration generation script *inside* the container. This script extracts the API Token and creates the necessary settings files (`.gemini/settings.json` and `.vscode/mcp.json`). + + ```bash + # Run inside the container + /workspaces/NetAlertX/.devcontainer/scripts/generate-configs.sh + ``` + +3. **Apply Changes:** + + * **For Gemini CLI:** + The agent session must be **restarted** to load the new `.gemini/settings.json`. + > "I have generated the MCP configuration. Please **restart this session** to activate the `netalertx-devcontainer` tools." + + * **For VS Code (GitHub Copilot / Cline):** + The VS Code window must be **reloaded** to pick up the new `.vscode/mcp.json`. + > "I have generated the MCP configuration. Please run **'Developer: Reload Window'** in VS Code to activate the MCP server." + +## Verification + +After restarting, you should see new tools available (e.g., `netalertx-devcontainer__get_devices`). diff --git a/.github/skills/mcp-activation/SKILL.md b/.github/skills/mcp-activation/SKILL.md new file mode 100644 index 00000000..abbdb317 --- /dev/null +++ b/.github/skills/mcp-activation/SKILL.md @@ -0,0 +1,34 @@ +--- +name: mcp-activation +description: Enables live interaction with the NetAlertX runtime. This skill configures the Model Context Protocol (MCP) connection, granting full API access for debugging, troubleshooting, and real-time operations including database queries, network scans, and device management. +--- + +# MCP Activation Skill + +This skill configures the environment to expose the Model Context Protocol (MCP) server to AI agents running inside the devcontainer. + +## Usage + +This skill assumes you are already running within the NetAlertX devcontainer. + +1. **Generate Configurations:** + Run the configuration generation script to extract the API Token and update the VS Code MCP settings. + + ```bash + /workspaces/NetAlertX/.devcontainer/scripts/generate-configs.sh + ``` + +2. **Reload Window:** + Request the user to reload the VS Code window to activate the new tools. + > I have generated the MCP configuration. Please run the **'Developer: Reload Window'** command to activate the MCP server tools. + > In VS Code: open the Command Palette (Windows/Linux: Ctrl+Shift+P, macOS: Cmd+Shift+P), type Developer: Reload Window, press Enter — or click the Reload button if a notification appears. 🔁 + > After you reload, tell me “Window reloaded” (or just “reloaded”) and I’ll continue. + + +## Why use this? + +Access the live runtime API to perform operations that are not possible through static file analysis: +- **Query the database** +- **Trigger network scans** +- **Manage devices and events** +- **Troubleshoot real-time system state** From 7caa6a1949a40a2d8082634bd216b58c9fa2d3d0 Mon Sep 17 00:00:00 2001 From: Adam Outler Date: Tue, 3 Feb 2026 18:32:35 +0100 Subject: [PATCH 3/4] feat(api): Add named events totals endpoint and deprecate raw version --- server/api_server/api_server_start.py | 32 +++++++++++++++++++++++++++ server/api_server/openapi/schemas.py | 18 +++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/server/api_server/api_server_start.py b/server/api_server/api_server_start.py index 17c68a1b..4937876b 100755 --- a/server/api_server/api_server_start.py +++ b/server/api_server/api_server_start.py @@ -73,6 +73,7 @@ from .openapi.schemas import ( # noqa: E402 [flake8 lint suppression] DeviceInfo, BaseResponse, DeviceTotalsResponse, DeviceTotalsNamedResponse, + EventsTotalsNamedResponse, DeleteDevicesRequest, DeviceImportRequest, DeviceImportResponse, UpdateDeviceColumnRequest, LockDeviceFieldRequest, UnlockDeviceFieldsRequest, @@ -1527,6 +1528,37 @@ def api_get_events_totals(payload=None): return jsonify(totals) +@app.route("/sessions/totals/named", methods=["GET"]) +@validate_request( + operation_id="get_events_totals_named", + summary="Get Named Event Totals", + description="Retrieve event/session totals with named fields for a specified period.", + query_params=[{ + "name": "period", + "description": "Time period (e.g., '7 days')", + "required": False, + "schema": {"type": "string", "default": "7 days"} + }], + response_model=EventsTotalsNamedResponse, + tags=["events"], + auth_callable=is_authorized +) +def api_get_events_totals_named(payload=None): + period = request.args.get("period", "7 days") + event_handler = EventInstance() + totals = event_handler.getEventsTotals(period) + # totals order: [all_events, sessions, missing, voided, new, down] + totals_dict = { + "total": totals[0] if len(totals) > 0 else 0, + "sessions": totals[1] if len(totals) > 1 else 0, + "missing": totals[2] if len(totals) > 2 else 0, + "voided": totals[3] if len(totals) > 3 else 0, + "new": totals[4] if len(totals) > 4 else 0, + "down": totals[5] if len(totals) > 5 else 0 + } + return jsonify({"success": True, "totals": totals_dict}) + + @app.route('/events/recent', methods=['GET', 'POST']) @validate_request( operation_id="get_recent_events", diff --git a/server/api_server/openapi/schemas.py b/server/api_server/openapi/schemas.py index b711e8ea..fc98ceec 100644 --- a/server/api_server/openapi/schemas.py +++ b/server/api_server/openapi/schemas.py @@ -318,6 +318,24 @@ class DeviceTotalsNamedResponse(BaseResponse): ) +class EventsTotalsNamedResponse(BaseResponse): + """Response with named event/session statistics.""" + totals: Dict[str, int] = Field( + ..., + description="Dictionary of counts: total, sessions, missing, voided, new, down", + json_schema_extra={ + "examples": [{ + "total": 100, + "sessions": 50, + "missing": 0, + "voided": 0, + "new": 5, + "down": 2 + }] + } + ) + + class DeviceExportRequest(BaseModel): """Request for exporting devices.""" format: Literal["csv", "json"] = Field( From 1139e0e190859a6e799422cd67030980d810c57e Mon Sep 17 00:00:00 2001 From: Adam Outler Date: Tue, 3 Feb 2026 18:43:43 +0100 Subject: [PATCH 4/4] docs(api): Deprecate raw events totals endpoint --- server/api_server/api_server_start.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/api_server/api_server_start.py b/server/api_server/api_server_start.py index 4937876b..c2108be5 100755 --- a/server/api_server/api_server_start.py +++ b/server/api_server/api_server_start.py @@ -1510,8 +1510,8 @@ def api_delete_old_events(days: int, payload=None): @app.route("/sessions/totals", methods=["GET"]) @validate_request( operation_id="get_events_totals", - summary="Get Events Totals", - description="Retrieve event totals for a specified period.", + summary="Get Events Totals (Deprecated)", + description="Retrieve event totals for a specified period. Deprecated: use /sessions/totals/named instead.", query_params=[{ "name": "period", "description": "Time period (e.g., '7 days')",