mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2025-12-07 09:36:05 -08:00
Compare commits
4 Commits
5d1c63375b
...
fix-pr-130
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1dee812ce6 | ||
|
|
5c44fd8fea | ||
|
|
bd691f01b1 | ||
|
|
624fd87ee7 |
2
.github/copilot-instructions.md
vendored
2
.github/copilot-instructions.md
vendored
@@ -43,7 +43,7 @@ Backend loop phases (see `server/__main__.py` and `server/plugin.py`): `once`, `
|
||||
|
||||
## Conventions & helpers to reuse
|
||||
- Settings: add/modify via `ccd()` in `server/initialise.py` or per‑plugin manifest. Never hardcode ports or secrets; use `get_setting_value()`.
|
||||
- Logging: use `logger.mylog(level, [message])`; levels: none/minimal/verbose/debug/trace.
|
||||
- Logging: use `mylog(level, [message])`; levels: none/minimal/verbose/debug/trace. `none` is used for most important messages that should always appear, such as exceptions.
|
||||
- Time/MAC/strings: `helper.py` (`timeNowDB`, `normalize_mac`, sanitizers). Validate MACs before DB writes.
|
||||
- DB helpers: prefer `server/db/db_helper.py` functions (e.g., `get_table_json`, device condition helpers) over raw SQL in new paths.
|
||||
|
||||
|
||||
22
Dockerfile
22
Dockerfile
@@ -26,15 +26,25 @@ ENV PATH="/opt/venv/bin:$PATH"
|
||||
|
||||
# Install build dependencies
|
||||
COPY requirements.txt /tmp/requirements.txt
|
||||
RUN apk add --no-cache bash shadow python3 python3-dev gcc musl-dev libffi-dev openssl-dev git rust cargo \
|
||||
RUN apk add --no-cache \
|
||||
bash \
|
||||
shadow \
|
||||
python3 \
|
||||
python3-dev \
|
||||
gcc \
|
||||
musl-dev \
|
||||
libffi-dev \
|
||||
openssl-dev \
|
||||
git \
|
||||
rust \
|
||||
cargo \
|
||||
&& python -m venv /opt/venv
|
||||
|
||||
# Create virtual environment owned by root, but readable by everyone else. This makes it easy to copy
|
||||
# into hardened stage without worrying about permissions and keeps image size small. Keeping the commands
|
||||
# together makes for a slightly smaller image size.
|
||||
RUN pip install --no-cache-dir -r /tmp/requirements.txt && \
|
||||
# Upgrade pip/wheel/setuptools and install Python packages
|
||||
RUN python -m pip install --upgrade pip setuptools wheel && \
|
||||
pip install --no-cache-dir -r /tmp/requirements.txt && \
|
||||
chmod -R u-rwx,g-rwx /opt
|
||||
|
||||
|
||||
# second stage is the main runtime stage with just the minimum required to run the application
|
||||
# The runner is used for both devcontainer, and as a base for the hardened stage.
|
||||
FROM alpine:3.22 AS runner
|
||||
|
||||
22
docs/API.md
22
docs/API.md
@@ -36,9 +36,15 @@ Authorization: Bearer <API_TOKEN>
|
||||
If the token is missing or invalid, the server will return:
|
||||
|
||||
```json
|
||||
{ "error": "Forbidden" }
|
||||
{
|
||||
"success": false,
|
||||
"message": "ERROR: Not authorized",
|
||||
"error": "Forbidden"
|
||||
}
|
||||
```
|
||||
|
||||
HTTP Status: **403 Forbidden**
|
||||
|
||||
---
|
||||
|
||||
## Base URL
|
||||
@@ -54,6 +60,8 @@ http://<server>:<GRAPHQL_PORT>/
|
||||
> [!TIP]
|
||||
> When retrieving devices or settings try using the GraphQL API endpoint first as it is read-optimized.
|
||||
|
||||
### Standard REST Endpoints
|
||||
|
||||
* [Device API Endpoints](API_DEVICE.md) – Manage individual devices
|
||||
* [Devices Collection](API_DEVICES.md) – Bulk operations on multiple devices
|
||||
* [Events](API_EVENTS.md) – Device event logging and management
|
||||
@@ -69,6 +77,18 @@ http://<server>:<GRAPHQL_PORT>/
|
||||
* [Logs](API_LOGS.md) – Purging of logs and adding to the event execution queue for user triggered events
|
||||
* [DB query](API_DBQUERY.md) (⚠ Internal) - Low level database access - use other endpoints if possible
|
||||
|
||||
### MCP Server Bridge
|
||||
|
||||
NetAlertX includes an **MCP (Model Context Protocol) Server Bridge** that provides AI assistants access to NetAlertX functionality through standardized tools. MCP endpoints are available at `/mcp/sse/*` paths and mirror the functionality of standard REST endpoints:
|
||||
|
||||
* `/mcp/sse` - Server-Sent Events endpoint for MCP client connections
|
||||
* `/mcp/sse/openapi.json` - OpenAPI specification for available MCP tools
|
||||
* `/mcp/sse/device/*`, `/mcp/sse/devices/*`, `/mcp/sse/nettools/*`, `/mcp/sse/events/*` - MCP-enabled versions of REST endpoints
|
||||
|
||||
MCP endpoints require the same Bearer token authentication as REST endpoints.
|
||||
|
||||
**📖 See [MCP Server Bridge API](API_MCP.md) for complete documentation, tool specifications, and integration examples.**
|
||||
|
||||
See [Testing](API_TESTS.md) for example requests and usage.
|
||||
|
||||
---
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
The **Database Query API** provides direct, low-level access to the NetAlertX database. It allows **read, write, update, and delete** operations against tables, using **base64-encoded** SQL or structured parameters.
|
||||
|
||||
> [!Warning]
|
||||
> [!Warning]
|
||||
> This API is primarily used internally to generate and render the application UI. These endpoints are low-level and powerful, and should be used with caution. Wherever possible, prefer the [standard API endpoints](API.md). Invalid or unsafe queries can corrupt data.
|
||||
> If you need data in a specific format that is not already provided, please open an issue or pull request with a clear, broadly useful use case. This helps ensure new endpoints benefit the wider community rather than relying on raw database queries.
|
||||
|
||||
@@ -16,10 +16,14 @@ All `/dbquery/*` endpoints require an API token in the HTTP headers:
|
||||
Authorization: Bearer <API_TOKEN>
|
||||
```
|
||||
|
||||
If the token is missing or invalid:
|
||||
If the token is missing or invalid (HTTP 403):
|
||||
|
||||
```json
|
||||
{ "error": "Forbidden" }
|
||||
{
|
||||
\"success\": false,
|
||||
\"message\": \"ERROR: Not authorized\",
|
||||
\"error\": \"Forbidden\"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -41,6 +41,8 @@ Manage a **single device** by its MAC address. Operations include retrieval, upd
|
||||
* Device not found → HTTP 404
|
||||
* Unauthorized → HTTP 403
|
||||
|
||||
**MCP Integration**: Available as `get_device_info` and `set_device_alias` tools. See [MCP Server Bridge API](API_MCP.md).
|
||||
|
||||
---
|
||||
|
||||
## 2. Update Device Fields
|
||||
|
||||
@@ -170,7 +170,7 @@ The Devices Collection API provides operations to **retrieve, manage, import/exp
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
[
|
||||
[
|
||||
120, // Total devices
|
||||
85, // Connected
|
||||
5, // Favorites
|
||||
@@ -207,6 +207,93 @@ The Devices Collection API provides operations to **retrieve, manage, import/exp
|
||||
|
||||
---
|
||||
|
||||
### 9. Search Devices
|
||||
|
||||
* **POST** `/devices/search`
|
||||
Search for devices by MAC, name, or IP address.
|
||||
|
||||
**Request Body** (JSON):
|
||||
|
||||
```json
|
||||
{
|
||||
"query": ".50"
|
||||
}
|
||||
```
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"devices": [
|
||||
{
|
||||
"devName": "Test Device",
|
||||
"devMac": "AA:BB:CC:DD:EE:FF",
|
||||
"devLastIP": "192.168.1.50"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 10. Get Latest Device
|
||||
|
||||
* **GET** `/devices/latest`
|
||||
Get the most recently connected device.
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"devName": "Latest Device",
|
||||
"devMac": "AA:BB:CC:DD:EE:FF",
|
||||
"devLastIP": "192.168.1.100",
|
||||
"devFirstConnection": "2025-12-07 10:30:00"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 11. Get Network Topology
|
||||
|
||||
* **GET** `/devices/network/topology`
|
||||
Get network topology showing device relationships.
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "AA:AA:AA:AA:AA:AA",
|
||||
"name": "Router",
|
||||
"vendor": "VendorA"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"source": "AA:AA:AA:AA:AA:AA",
|
||||
"target": "BB:BB:BB:BB:BB:BB",
|
||||
"port": "eth1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MCP Tools
|
||||
|
||||
These endpoints are also available as **MCP Tools** for AI assistant integration:
|
||||
- `list_devices`, `search_devices`, `get_latest_device`, `get_network_topology`, `set_device_alias`
|
||||
|
||||
📖 See [MCP Server Bridge API](API_MCP.md) for AI integration details.
|
||||
|
||||
---
|
||||
|
||||
## Example `curl` Requests
|
||||
|
||||
**Get All Devices**:
|
||||
@@ -247,3 +334,26 @@ curl -X GET "http://<server_ip>:<GRAPHQL_PORT>/devices/by-status?status=online"
|
||||
-H "Authorization: Bearer <API_TOKEN>"
|
||||
```
|
||||
|
||||
**Search Devices**:
|
||||
|
||||
```sh
|
||||
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/devices/search" \
|
||||
-H "Authorization: Bearer <API_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"query": "192.168.1"}'
|
||||
```
|
||||
|
||||
**Get Latest Device**:
|
||||
|
||||
```sh
|
||||
curl -X GET "http://<server_ip>:<GRAPHQL_PORT>/devices/latest" \
|
||||
-H "Authorization: Bearer <API_TOKEN>"
|
||||
```
|
||||
|
||||
**Get Network Topology**:
|
||||
|
||||
```sh
|
||||
curl -X GET "http://<server_ip>:<GRAPHQL_PORT>/devices/network/topology" \
|
||||
-H "Authorization: Bearer <API_TOKEN>"
|
||||
```
|
||||
|
||||
|
||||
@@ -88,7 +88,56 @@ The Events API provides access to **device event logs**, allowing creation, retr
|
||||
|
||||
---
|
||||
|
||||
### 4. Event Totals Over a Period
|
||||
### 4. Get Recent Events
|
||||
|
||||
* **GET** `/events/recent` → Get events from the last 24 hours
|
||||
* **GET** `/events/<hours>` → Get events from the last N hours
|
||||
|
||||
**Response** (JSON):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"hours": 24,
|
||||
"count": 5,
|
||||
"events": [
|
||||
{
|
||||
"eve_DateTime": "2025-12-07 12:00:00",
|
||||
"eve_EventType": "New Device",
|
||||
"eve_MAC": "AA:BB:CC:DD:EE:FF",
|
||||
"eve_IP": "192.168.1.100",
|
||||
"eve_AdditionalInfo": "Device detected"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. Get Latest Events
|
||||
|
||||
* **GET** `/events/last`
|
||||
Get the 10 most recent events.
|
||||
|
||||
**Response** (JSON):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"count": 10,
|
||||
"events": [
|
||||
{
|
||||
"eve_DateTime": "2025-12-07 12:00:00",
|
||||
"eve_EventType": "Device Down",
|
||||
"eve_MAC": "AA:BB:CC:DD:EE:FF"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. Event Totals Over a Period
|
||||
|
||||
* **GET** `/sessions/totals?period=<period>`
|
||||
Return event and session totals over a given period.
|
||||
@@ -116,12 +165,25 @@ The Events API provides access to **device event logs**, allowing creation, retr
|
||||
|
||||
---
|
||||
|
||||
## MCP Tools
|
||||
|
||||
Event endpoints are available as **MCP Tools** for AI assistant integration:
|
||||
- `get_recent_alerts`, `get_last_events`
|
||||
|
||||
📖 See [MCP Server Bridge API](API_MCP.md) for AI integration details.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
* All endpoints require **authorization** (Bearer token). Unauthorized requests return:
|
||||
* All endpoints require **authorization** (Bearer token). Unauthorized requests return HTTP 403:
|
||||
|
||||
```json
|
||||
{ "error": "Forbidden" }
|
||||
{
|
||||
"success": false,
|
||||
"message": "ERROR: Not authorized",
|
||||
"error": "Forbidden"
|
||||
}
|
||||
```
|
||||
|
||||
* Events are stored in the **Events table** with the following fields:
|
||||
|
||||
326
docs/API_MCP.md
Normal file
326
docs/API_MCP.md
Normal file
@@ -0,0 +1,326 @@
|
||||
# MCP Server Bridge API
|
||||
|
||||
The **MCP (Model Context Protocol) Server Bridge** provides AI assistants with standardized access to NetAlertX functionality through tools and server-sent events. This enables AI systems to interact with your network monitoring data in real-time.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The MCP Server Bridge exposes NetAlertX functionality as **MCP Tools** that AI assistants can call to:
|
||||
|
||||
- Search and retrieve device information
|
||||
- Trigger network scans
|
||||
- Get network topology and events
|
||||
- Wake devices via Wake-on-LAN
|
||||
- Access open port information
|
||||
- Set device aliases
|
||||
|
||||
All MCP endpoints mirror the functionality of standard REST endpoints but are optimized for AI assistant integration.
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
MCP endpoints use the same **Bearer token authentication** as REST endpoints:
|
||||
|
||||
```http
|
||||
Authorization: Bearer <API_TOKEN>
|
||||
```
|
||||
|
||||
Unauthorized requests return HTTP 403:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "ERROR: Not authorized",
|
||||
"error": "Forbidden"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MCP Connection Endpoint
|
||||
|
||||
### Server-Sent Events (SSE)
|
||||
|
||||
* **GET/POST** `/mcp/sse`
|
||||
|
||||
Main MCP connection endpoint for AI clients. Establishes a persistent connection using Server-Sent Events for real-time communication between AI assistants and NetAlertX.
|
||||
|
||||
**Connection Example**:
|
||||
|
||||
```javascript
|
||||
const eventSource = new EventSource('/mcp/sse', {
|
||||
headers: {
|
||||
'Authorization': 'Bearer <API_TOKEN>'
|
||||
}
|
||||
});
|
||||
|
||||
eventSource.onmessage = function(event) {
|
||||
const response = JSON.parse(event.data);
|
||||
console.log('MCP Response:', response);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## OpenAPI Specification
|
||||
|
||||
### Get MCP Tools Specification
|
||||
|
||||
* **GET** `/mcp/sse/openapi.json`
|
||||
|
||||
Returns the OpenAPI specification for all available MCP tools, describing the parameters and schemas for each tool.
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"title": "NetAlertX Tools",
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"servers": [{"url": "/"}],
|
||||
"paths": {
|
||||
"/devices/by-status": {
|
||||
"post": {"operationId": "list_devices"}
|
||||
},
|
||||
"/device/{mac}": {
|
||||
"post": {"operationId": "get_device_info"}
|
||||
},
|
||||
"/devices/search": {
|
||||
"post": {"operationId": "search_devices"}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Available MCP Tools
|
||||
|
||||
### Device Management Tools
|
||||
|
||||
| Tool | Endpoint | Description |
|
||||
|------|----------|-------------|
|
||||
| `list_devices` | `/mcp/sse/devices/by-status` | List devices by online status |
|
||||
| `get_device_info` | `/mcp/sse/device/<mac>` | Get detailed device information |
|
||||
| `search_devices` | `/mcp/sse/devices/search` | Search devices by MAC, name, or IP |
|
||||
| `get_latest_device` | `/mcp/sse/devices/latest` | Get most recently connected device |
|
||||
| `set_device_alias` | `/mcp/sse/device/<mac>/set-alias` | Set device friendly name |
|
||||
|
||||
### Network Tools
|
||||
|
||||
| Tool | Endpoint | Description |
|
||||
|------|----------|-------------|
|
||||
| `trigger_scan` | `/mcp/sse/nettools/trigger-scan` | Trigger network discovery scan |
|
||||
| `get_open_ports` | `/mcp/sse/device/open_ports` | Get stored NMAP open ports for device |
|
||||
| `wol_wake_device` | `/mcp/sse/nettools/wakeonlan` | Wake device using Wake-on-LAN |
|
||||
| `get_network_topology` | `/mcp/sse/devices/network/topology` | Get network topology map |
|
||||
|
||||
### Event & Monitoring Tools
|
||||
|
||||
| Tool | Endpoint | Description |
|
||||
|------|----------|-------------|
|
||||
| `get_recent_alerts` | `/mcp/sse/events/recent` | Get events from last 24 hours |
|
||||
| `get_last_events` | `/mcp/sse/events/last` | Get 10 most recent events |
|
||||
|
||||
---
|
||||
|
||||
## Tool Usage Examples
|
||||
|
||||
### Search Devices Tool
|
||||
|
||||
**Tool Call**:
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "1",
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "search_devices",
|
||||
"arguments": {
|
||||
"query": "192.168.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "1",
|
||||
"result": {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "{\n \"success\": true,\n \"devices\": [\n {\n \"devName\": \"Router\",\n \"devMac\": \"AA:BB:CC:DD:EE:FF\",\n \"devLastIP\": \"192.168.1.1\"\n }\n ]\n}"
|
||||
}
|
||||
],
|
||||
"isError": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Trigger Network Scan Tool
|
||||
|
||||
**Tool Call**:
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "2",
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "trigger_scan",
|
||||
"arguments": {
|
||||
"type": "ARPSCAN"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "2",
|
||||
"result": {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "{\n \"success\": true,\n \"message\": \"Scan triggered for type: ARPSCAN\"\n}"
|
||||
}
|
||||
],
|
||||
"isError": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Wake-on-LAN Tool
|
||||
|
||||
**Tool Call**:
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "3",
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "wol_wake_device",
|
||||
"arguments": {
|
||||
"devMac": "AA:BB:CC:DD:EE:FF"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration with AI Assistants
|
||||
|
||||
### Claude Desktop Integration
|
||||
|
||||
Add to your Claude Desktop `mcp.json` configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcp": {
|
||||
"servers": {
|
||||
"netalertx": {
|
||||
"command": "node",
|
||||
"args": ["/path/to/mcp-client.js"],
|
||||
"env": {
|
||||
"NETALERTX_URL": "http://your-server:<GRAPHQL_PORT>",
|
||||
"NETALERTX_TOKEN": "your-api-token"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Generic MCP Client
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import json
|
||||
from mcp import ClientSession, StdioServerParameters
|
||||
from mcp.client.stdio import stdio_client
|
||||
|
||||
async def main():
|
||||
# Connect to NetAlertX MCP server
|
||||
server_params = StdioServerParameters(
|
||||
command="curl",
|
||||
args=[
|
||||
"-N", "-H", "Authorization: Bearer <API_TOKEN>",
|
||||
"http://your-server:<GRAPHQL_PORT>/mcp/sse"
|
||||
]
|
||||
)
|
||||
|
||||
async with stdio_client(server_params) as (read, write):
|
||||
async with ClientSession(read, write) as session:
|
||||
# Initialize connection
|
||||
await session.initialize()
|
||||
|
||||
# List available tools
|
||||
tools = await session.list_tools()
|
||||
print(f"Available tools: {[t.name for t in tools.tools]}")
|
||||
|
||||
# Call a tool
|
||||
result = await session.call_tool("search_devices", {"query": "router"})
|
||||
print(f"Search result: {result}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
MCP tool calls return structured error information:
|
||||
|
||||
**Error Response**:
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "1",
|
||||
"result": {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Error calling tool: Device not found"
|
||||
}
|
||||
],
|
||||
"isError": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Common Error Types**:
|
||||
- `401/403` - Authentication failure
|
||||
- `400` - Invalid parameters or missing required fields
|
||||
- `404` - Resource not found (device, scan results, etc.)
|
||||
- `500` - Internal server error
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
* MCP endpoints require the same API token authentication as REST endpoints
|
||||
* All MCP tools return JSON responses wrapped in MCP protocol format
|
||||
* Server-Sent Events maintain persistent connections for real-time updates
|
||||
* Tool parameters match their REST endpoint equivalents
|
||||
* Error responses include both HTTP status codes and descriptive messages
|
||||
* MCP bridge automatically handles request/response serialization
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
* [Main API Overview](API.md) - Core REST API documentation
|
||||
* [Device API](API_DEVICE.md) - Individual device management
|
||||
* [Devices Collection API](API_DEVICES.md) - Bulk device operations
|
||||
* [Network Tools API](API_NETTOOLS.md) - Wake-on-LAN, scans, network utilities
|
||||
* [Events API](API_EVENTS.md) - Event logging and monitoring
|
||||
@@ -241,3 +241,12 @@ curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/nettools/nmap" \
|
||||
curl "http://<server_ip>:<GRAPHQL_PORT>/nettools/internetinfo" \
|
||||
-H "Authorization: Bearer <API_TOKEN>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MCP Tools
|
||||
|
||||
Network tools are available as **MCP Tools** for AI assistant integration:
|
||||
- `wol_wake_device`, `trigger_scan`, `get_open_ports`
|
||||
|
||||
📖 See [MCP Server Bridge API](API_MCP.md) for AI integration details.
|
||||
|
||||
@@ -98,6 +98,7 @@ nav:
|
||||
- Sync: API_SYNC.md
|
||||
- GraphQL: API_GRAPHQL.md
|
||||
- DB query: API_DBQUERY.md
|
||||
- MCP: API_MCP.md
|
||||
- Tests: API_TESTS.md
|
||||
- SUPERSEDED OLD API Overview: API_OLD.md
|
||||
- Integrations:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
cryptography<40
|
||||
openwrt-luci-rpc
|
||||
asusrouter
|
||||
aiohttp
|
||||
|
||||
@@ -3,7 +3,6 @@ import sys
|
||||
import os
|
||||
|
||||
from flask import Flask, request, jsonify, Response
|
||||
import requests
|
||||
from models.device_instance import DeviceInstance # noqa: E402
|
||||
from flask_cors import CORS
|
||||
|
||||
@@ -116,26 +115,10 @@ CORS(
|
||||
# MCP bridge variables + helpers (moved from mcp_routes)
|
||||
# -------------------------------------------------------------------------------
|
||||
|
||||
mcp_openapi_spec_cache = None
|
||||
|
||||
BACKEND_PORT = get_setting_value("GRAPHQL_PORT")
|
||||
API_BASE_URL = f"http://localhost:{BACKEND_PORT}"
|
||||
|
||||
|
||||
def get_openapi_spec_local():
|
||||
global mcp_openapi_spec_cache
|
||||
if mcp_openapi_spec_cache:
|
||||
return mcp_openapi_spec_cache
|
||||
try:
|
||||
resp = requests.get(f"{API_BASE_URL}/mcp/openapi.json", timeout=10)
|
||||
resp.raise_for_status()
|
||||
mcp_openapi_spec_cache = resp.json()
|
||||
return mcp_openapi_spec_cache
|
||||
except Exception as e:
|
||||
mylog('minimal', [f"Error fetching OpenAPI spec: {e}"])
|
||||
return None
|
||||
|
||||
|
||||
@app.route('/mcp/sse', methods=['GET', 'POST'])
|
||||
def api_mcp_sse():
|
||||
if not is_authorized():
|
||||
@@ -169,10 +152,12 @@ def log_request_info():
|
||||
|
||||
@app.errorhandler(404)
|
||||
def not_found(error):
|
||||
# Get the requested path from the request object instead of error.description
|
||||
requested_url = request.path if request else "unknown"
|
||||
response = {
|
||||
"success": False,
|
||||
"error": "API route not found",
|
||||
"message": f"The requested URL {error.description if hasattr(error, 'description') else ''} was not found on the server.",
|
||||
"message": f"The requested URL {requested_url} was not found on the server.",
|
||||
}
|
||||
return jsonify(response), 404
|
||||
|
||||
@@ -195,7 +180,7 @@ def graphql_endpoint():
|
||||
if not is_authorized():
|
||||
msg = '[graphql_server] Unauthorized access attempt - make sure your GRAPHQL_PORT and API_TOKEN settings are correct.'
|
||||
mylog('verbose', [msg])
|
||||
return jsonify({"success": False, "message": msg, "error": "Forbidden"}), 401
|
||||
return jsonify({"success": False, "message": msg, "error": "Forbidden"}), 403
|
||||
|
||||
# Retrieve and log request data
|
||||
data = request.get_json()
|
||||
@@ -316,7 +301,7 @@ def api_device_set_alias(mac):
|
||||
def api_device_open_ports():
|
||||
"""Get stored NMAP open ports for a target IP or MAC."""
|
||||
if not is_authorized():
|
||||
return jsonify({"success": False, "error": "Unauthorized"}), 401
|
||||
return jsonify({"success": False, "message": "ERROR: Not authorized", "error": "Forbidden"}), 403
|
||||
|
||||
data = request.get_json(silent=True) or {}
|
||||
target = data.get('target')
|
||||
@@ -408,7 +393,7 @@ def api_devices_by_status():
|
||||
def api_devices_search():
|
||||
"""Device search: accepts 'query' in JSON and maps to device info/search."""
|
||||
if not is_authorized():
|
||||
return jsonify({"error": "Unauthorized"}), 401
|
||||
return jsonify({"success": False, "message": "ERROR: Not authorized", "error": "Forbidden"}), 403
|
||||
|
||||
data = request.get_json(silent=True) or {}
|
||||
query = data.get('query')
|
||||
@@ -439,7 +424,7 @@ def api_devices_search():
|
||||
def api_devices_latest():
|
||||
"""Get latest device (most recent) - maps to DeviceInstance.getLatest()."""
|
||||
if not is_authorized():
|
||||
return jsonify({"error": "Unauthorized"}), 401
|
||||
return jsonify({"success": False, "message": "ERROR: Not authorized", "error": "Forbidden"}), 403
|
||||
|
||||
device_handler = DeviceInstance()
|
||||
|
||||
@@ -455,7 +440,7 @@ def api_devices_latest():
|
||||
def api_devices_network_topology():
|
||||
"""Network topology mapping."""
|
||||
if not is_authorized():
|
||||
return jsonify({"error": "Unauthorized"}), 401
|
||||
return jsonify({"success": False, "message": "ERROR: Not authorized", "error": "Forbidden"}), 403
|
||||
|
||||
device_handler = DeviceInstance()
|
||||
|
||||
@@ -485,6 +470,11 @@ def api_wakeonlan():
|
||||
if not dev or not dev.get('devMac'):
|
||||
return jsonify({"success": False, "message": "ERROR: Device not found", "error": "MAC not resolved"}), 404
|
||||
mac = dev.get('devMac')
|
||||
|
||||
# Validate that we have a valid MAC address
|
||||
if not mac:
|
||||
return jsonify({"success": False, "message": "ERROR: Missing device MAC or IP", "error": "Bad Request"}), 400
|
||||
|
||||
return wakeonlan(mac)
|
||||
|
||||
|
||||
@@ -574,7 +564,7 @@ def api_trigger_scan():
|
||||
@app.route('/mcp/sse/openapi.json', methods=['GET'])
|
||||
def api_openapi_spec():
|
||||
if not is_authorized():
|
||||
return jsonify({"success": False, "error": "Unauthorized"}), 401
|
||||
return jsonify({"success": False, "message": "ERROR: Not authorized", "error": "Forbidden"}), 403
|
||||
return openapi_spec()
|
||||
|
||||
|
||||
@@ -787,7 +777,8 @@ def get_last_events():
|
||||
# Create fresh DB instance for this thread
|
||||
event_handler = EventInstance()
|
||||
|
||||
return event_handler.get_last_n(10)
|
||||
events = event_handler.get_last_n(10)
|
||||
return jsonify({"success": True, "count": len(events), "events": events}), 200
|
||||
|
||||
|
||||
@app.route('/events/<int:hours>', methods=['GET'])
|
||||
@@ -795,7 +786,7 @@ def api_events_recent(hours):
|
||||
"""Return events from the last <hours> hours using EventInstance."""
|
||||
|
||||
if not is_authorized():
|
||||
return jsonify({"success": False, "error": "Unauthorized"}), 401
|
||||
return jsonify({"success": False, "message": "ERROR: Not authorized", "error": "Forbidden"}), 403
|
||||
|
||||
# Validate hours input
|
||||
if hours <= 0:
|
||||
|
||||
@@ -71,7 +71,8 @@ def get_openapi_spec():
|
||||
r.raise_for_status()
|
||||
_openapi_spec_cache = r.json()
|
||||
return _openapi_spec_cache
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
mylog("none", [f"[MCP] Failed to fetch OpenAPI spec: {e}"])
|
||||
return None
|
||||
|
||||
|
||||
@@ -140,11 +141,13 @@ def process_mcp_request(data):
|
||||
try:
|
||||
json_content = api_res.json()
|
||||
content.append({'type': 'text', 'text': json.dumps(json_content, indent=2)})
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
mylog("none", [f"[MCP] Failed to parse API response as JSON: {e}"])
|
||||
content.append({'type': 'text', 'text': api_res.text})
|
||||
is_error = api_res.status_code >= 400
|
||||
return {'jsonrpc': '2.0', 'id': msg_id, 'result': {'content': content, 'isError': is_error}}
|
||||
except Exception as e:
|
||||
mylog("none", [f"[MCP] Error calling tool {tool_name}: {e}"])
|
||||
return {'jsonrpc': '2.0', 'id': msg_id, 'result': {'content': [{'type': 'text', 'text': f"Error calling tool: {str(e)}"}], 'isError': True}}
|
||||
if method == 'ping':
|
||||
return {'jsonrpc': '2.0', 'id': msg_id, 'result': {}}
|
||||
@@ -180,7 +183,7 @@ def mcp_sse():
|
||||
else:
|
||||
return '', 202
|
||||
except Exception as e:
|
||||
mylog("none", f'SSE POST processing error: {e}')
|
||||
mylog("none", [f"[MCP] SSE POST processing error: {e}"])
|
||||
return jsonify({'status': 'ok', 'message': 'MCP SSE endpoint active'}), 200
|
||||
|
||||
session_id = uuid.uuid4().hex
|
||||
|
||||
Reference in New Issue
Block a user