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** diff --git a/server/api_server/api_server_start.py b/server/api_server/api_server_start.py index 17c68a1b..c2108be5 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, @@ -1509,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')", @@ -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/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"}] 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(