Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| af96378cb6 | |||
| c5da11aa4c | |||
| 5e4667a643 | |||
| 093ac5b5ba |
@@ -1,3 +1,13 @@
|
|||||||
|
## v0.29.0 (2025-11-09)
|
||||||
|
|
||||||
|
### Feat
|
||||||
|
|
||||||
|
- **helm**: Add observability support with ServiceMonitor and Grafana dashboard
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
- **readiness**: Only check external Qdrant in network mode
|
||||||
|
|
||||||
## v0.28.0 (2025-11-09)
|
## v0.28.0 (2025-11-09)
|
||||||
|
|
||||||
### Feat
|
### Feat
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ apiVersion: v2
|
|||||||
name: nextcloud-mcp-server
|
name: nextcloud-mcp-server
|
||||||
description: A Helm chart for Nextcloud MCP Server - enables AI assistants to interact with Nextcloud
|
description: A Helm chart for Nextcloud MCP Server - enables AI assistants to interact with Nextcloud
|
||||||
type: application
|
type: application
|
||||||
version: 0.28.0
|
version: 0.29.0
|
||||||
appVersion: "0.28.0"
|
appVersion: "0.29.0"
|
||||||
keywords:
|
keywords:
|
||||||
- nextcloud
|
- nextcloud
|
||||||
- mcp
|
- mcp
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# Grafana Dashboards
|
||||||
|
|
||||||
|
This directory contains example Grafana dashboards for monitoring the Nextcloud MCP Server.
|
||||||
|
|
||||||
|
## Dashboards
|
||||||
|
|
||||||
|
### nextcloud-mcp-server.json
|
||||||
|
|
||||||
|
Comprehensive dashboard with the following panels:
|
||||||
|
|
||||||
|
- **Request Rate**: HTTP requests per second by method and endpoint
|
||||||
|
- **Error Rate**: Percentage of 5xx errors
|
||||||
|
- **Request Latency**: P50 and P95 latency by endpoint
|
||||||
|
- **Top MCP Tools**: Most frequently called tools
|
||||||
|
- **Nextcloud API Latency**: API call latency by app (notes, calendar, etc.)
|
||||||
|
- **Vector Sync Queue**: Queue size for background document processing
|
||||||
|
|
||||||
|
## Importing to Grafana
|
||||||
|
|
||||||
|
### Manual Import
|
||||||
|
|
||||||
|
1. Open Grafana UI
|
||||||
|
2. Navigate to Dashboards → Import
|
||||||
|
3. Upload `nextcloud-mcp-server.json`
|
||||||
|
4. Select your Prometheus data source
|
||||||
|
5. Click "Import"
|
||||||
|
|
||||||
|
### Automated Import (Kubernetes)
|
||||||
|
|
||||||
|
If using the Grafana Operator or kube-prometheus-stack, you can create a ConfigMap:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl create configmap nextcloud-mcp-dashboards \
|
||||||
|
--from-file=nextcloud-mcp-server.json \
|
||||||
|
-n monitoring
|
||||||
|
|
||||||
|
# Add label for Grafana sidecar to discover
|
||||||
|
kubectl label configmap nextcloud-mcp-dashboards \
|
||||||
|
grafana_dashboard=1 \
|
||||||
|
-n monitoring
|
||||||
|
```
|
||||||
|
|
||||||
|
Or add to your Helm values:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# values.yaml for kube-prometheus-stack
|
||||||
|
grafana:
|
||||||
|
dashboardProviders:
|
||||||
|
dashboardproviders.yaml:
|
||||||
|
apiVersion: 1
|
||||||
|
providers:
|
||||||
|
- name: 'nextcloud-mcp'
|
||||||
|
orgId: 1
|
||||||
|
folder: 'Nextcloud MCP'
|
||||||
|
type: file
|
||||||
|
disableDeletion: false
|
||||||
|
editable: true
|
||||||
|
options:
|
||||||
|
path: /var/lib/grafana/dashboards/nextcloud-mcp
|
||||||
|
|
||||||
|
dashboardsConfigMaps:
|
||||||
|
nextcloud-mcp: nextcloud-mcp-dashboards
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dashboard Variables
|
||||||
|
|
||||||
|
The dashboard includes two variables:
|
||||||
|
|
||||||
|
- **Data Source**: Select your Prometheus data source
|
||||||
|
- **Namespace**: Filter metrics by Kubernetes namespace
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
You can customize the dashboard by:
|
||||||
|
|
||||||
|
1. Adjusting refresh rate (default: 30s)
|
||||||
|
2. Modifying time range (default: last 6 hours)
|
||||||
|
3. Adding new panels for specific metrics
|
||||||
|
4. Adjusting thresholds in existing panels
|
||||||
|
|
||||||
|
## Metrics Reference
|
||||||
|
|
||||||
|
All metrics are documented in `/docs/observability.md`. Key metric prefixes:
|
||||||
|
|
||||||
|
- `mcp_http_*` - HTTP server metrics
|
||||||
|
- `mcp_tool_*` - MCP tool invocation metrics
|
||||||
|
- `mcp_nextcloud_api_*` - Nextcloud API call metrics
|
||||||
|
- `mcp_oauth_*` - OAuth token validation metrics
|
||||||
|
- `mcp_vector_sync_*` - Vector database sync metrics
|
||||||
|
- `mcp_db_*` - Database operation metrics
|
||||||
@@ -0,0 +1,630 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"builtIn": 1,
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana",
|
||||||
|
"uid": "-- Grafana --"
|
||||||
|
},
|
||||||
|
"enable": true,
|
||||||
|
"hide": true,
|
||||||
|
"iconColor": "rgba(0, 211, 255, 1)",
|
||||||
|
"name": "Annotations & Alerts",
|
||||||
|
"type": "dashboard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"id": null,
|
||||||
|
"links": [],
|
||||||
|
"liveNow": false,
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 10,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false,
|
||||||
|
"legend": false
|
||||||
|
},
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "never",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "reqps"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 1,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": ["mean", "max"],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"expr": "sum(rate(mcp_http_requests_total{namespace=\"$namespace\"}[5m])) by (method, endpoint)",
|
||||||
|
"legendFormat": "{{method}} {{endpoint}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Request Rate",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 10,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false,
|
||||||
|
"legend": false
|
||||||
|
},
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "never",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "line"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"max": 100,
|
||||||
|
"min": 0,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "yellow",
|
||||||
|
"value": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "percent"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 2,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": ["mean", "max"],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"expr": "sum(rate(mcp_http_requests_total{status_code=~\"5..\", namespace=\"$namespace\"}[5m])) / sum(rate(mcp_http_requests_total{namespace=\"$namespace\"}[5m])) * 100",
|
||||||
|
"legendFormat": "Error Rate",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Error Rate (%)",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 10,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false,
|
||||||
|
"legend": false
|
||||||
|
},
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "never",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "s"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 8
|
||||||
|
},
|
||||||
|
"id": 3,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": ["mean", "max"],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"expr": "histogram_quantile(0.95, sum(rate(mcp_http_request_duration_seconds_bucket{namespace=\"$namespace\"}[5m])) by (le, endpoint))",
|
||||||
|
"legendFormat": "{{endpoint}} (p95)",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"expr": "histogram_quantile(0.50, sum(rate(mcp_http_request_duration_seconds_bucket{namespace=\"$namespace\"}[5m])) by (le, endpoint))",
|
||||||
|
"legendFormat": "{{endpoint}} (p50)",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Request Latency (P50/P95)",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 10,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false,
|
||||||
|
"legend": false
|
||||||
|
},
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "never",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 8
|
||||||
|
},
|
||||||
|
"id": 4,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": ["mean", "max"],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"expr": "topk(10, sum(rate(mcp_tool_calls_total{namespace=\"$namespace\"}[5m])) by (tool_name))",
|
||||||
|
"legendFormat": "{{tool_name}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Top MCP Tools by Volume",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 10,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false,
|
||||||
|
"legend": false
|
||||||
|
},
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "never",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "s"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 16
|
||||||
|
},
|
||||||
|
"id": 5,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": ["mean", "max"],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"expr": "histogram_quantile(0.95, sum(rate(mcp_nextcloud_api_duration_seconds_bucket{namespace=\"$namespace\"}[5m])) by (le, app))",
|
||||||
|
"legendFormat": "{{app}} (p95)",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Nextcloud API Latency by App",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 10,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false,
|
||||||
|
"legend": false
|
||||||
|
},
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "never",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 16
|
||||||
|
},
|
||||||
|
"id": 6,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": ["mean", "lastNotNull"],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"expr": "mcp_vector_sync_queue_size{namespace=\"$namespace\"}",
|
||||||
|
"legendFormat": "Queue Size",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Vector Sync Queue Size",
|
||||||
|
"type": "timeseries"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refresh": "30s",
|
||||||
|
"schemaVersion": 38,
|
||||||
|
"style": "dark",
|
||||||
|
"tags": ["nextcloud", "mcp", "observability"],
|
||||||
|
"templating": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"current": {
|
||||||
|
"selected": false,
|
||||||
|
"text": "Prometheus",
|
||||||
|
"value": "Prometheus"
|
||||||
|
},
|
||||||
|
"hide": 0,
|
||||||
|
"includeAll": false,
|
||||||
|
"label": "Data Source",
|
||||||
|
"multi": false,
|
||||||
|
"name": "datasource",
|
||||||
|
"options": [],
|
||||||
|
"query": "prometheus",
|
||||||
|
"refresh": 1,
|
||||||
|
"regex": "",
|
||||||
|
"skipUrlSync": false,
|
||||||
|
"type": "datasource"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"current": {
|
||||||
|
"selected": false,
|
||||||
|
"text": "default",
|
||||||
|
"value": "default"
|
||||||
|
},
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"definition": "label_values(mcp_http_requests_total, namespace)",
|
||||||
|
"hide": 0,
|
||||||
|
"includeAll": false,
|
||||||
|
"label": "Namespace",
|
||||||
|
"multi": false,
|
||||||
|
"name": "namespace",
|
||||||
|
"options": [],
|
||||||
|
"query": {
|
||||||
|
"query": "label_values(mcp_http_requests_total, namespace)",
|
||||||
|
"refId": "PrometheusVariableQueryEditor-VariableQuery"
|
||||||
|
},
|
||||||
|
"refresh": 1,
|
||||||
|
"regex": "",
|
||||||
|
"skipUrlSync": false,
|
||||||
|
"sort": 0,
|
||||||
|
"type": "query"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-6h",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timepicker": {},
|
||||||
|
"timezone": "",
|
||||||
|
"title": "Nextcloud MCP Server",
|
||||||
|
"uid": "nextcloud-mcp-server",
|
||||||
|
"version": 1,
|
||||||
|
"weekStart": ""
|
||||||
|
}
|
||||||
@@ -56,6 +56,11 @@ spec:
|
|||||||
- name: http
|
- name: http
|
||||||
containerPort: {{ include "nextcloud-mcp-server.port" . }}
|
containerPort: {{ include "nextcloud-mcp-server.port" . }}
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
|
{{- if .Values.observability.metrics.enabled }}
|
||||||
|
- name: metrics
|
||||||
|
containerPort: {{ .Values.observability.metrics.port }}
|
||||||
|
protocol: TCP
|
||||||
|
{{- end }}
|
||||||
env:
|
env:
|
||||||
# Nextcloud connection
|
# Nextcloud connection
|
||||||
- name: NEXTCLOUD_HOST
|
- name: NEXTCLOUD_HOST
|
||||||
@@ -200,6 +205,27 @@ spec:
|
|||||||
value: {{ .Values.openai.baseUrl | quote }}
|
value: {{ .Values.openai.baseUrl | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
# Observability
|
||||||
|
- name: METRICS_ENABLED
|
||||||
|
value: {{ .Values.observability.metrics.enabled | quote }}
|
||||||
|
- name: METRICS_PORT
|
||||||
|
value: {{ .Values.observability.metrics.port | quote }}
|
||||||
|
{{- if .Values.observability.tracing.enabled }}
|
||||||
|
- name: OTEL_ENABLED
|
||||||
|
value: "true"
|
||||||
|
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||||
|
value: {{ .Values.observability.tracing.endpoint | quote }}
|
||||||
|
- name: OTEL_SERVICE_NAME
|
||||||
|
value: {{ .Values.observability.tracing.serviceName | quote }}
|
||||||
|
- name: OTEL_TRACES_SAMPLER_ARG
|
||||||
|
value: {{ .Values.observability.tracing.samplingRate | quote }}
|
||||||
|
{{- end }}
|
||||||
|
- name: LOG_FORMAT
|
||||||
|
value: {{ .Values.observability.logging.format | quote }}
|
||||||
|
- name: LOG_LEVEL
|
||||||
|
value: {{ .Values.observability.logging.level | quote }}
|
||||||
|
- name: LOG_INCLUDE_TRACE_CONTEXT
|
||||||
|
value: {{ .Values.observability.logging.includeTraceContext | quote }}
|
||||||
{{- with .Values.extraEnv }}
|
{{- with .Values.extraEnv }}
|
||||||
{{- toYaml . | nindent 12 }}
|
{{- toYaml . | nindent 12 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
{{- if and .Values.observability.metrics.enabled .Values.prometheusRule.enabled }}
|
||||||
|
apiVersion: monitoring.coreos.com/v1
|
||||||
|
kind: PrometheusRule
|
||||||
|
metadata:
|
||||||
|
name: {{ include "nextcloud-mcp-server.fullname" . }}
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
{{- include "nextcloud-mcp-server.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.prometheusRule.labels }}
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
groups:
|
||||||
|
- name: nextcloud-mcp-server.critical
|
||||||
|
interval: 30s
|
||||||
|
rules:
|
||||||
|
- alert: NextcloudMCPServerDown
|
||||||
|
expr: up{job="{{ include "nextcloud-mcp-server.fullname" . }}"} == 0
|
||||||
|
for: 5m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
annotations:
|
||||||
|
summary: "Nextcloud MCP Server is down"
|
||||||
|
description: "{{ `{{` }} $labels.pod {{ `}}` }} has been down for more than 5 minutes."
|
||||||
|
|
||||||
|
- alert: NextcloudMCPHighErrorRate
|
||||||
|
expr: |
|
||||||
|
sum(rate(mcp_http_requests_total{status_code=~"5..", job="{{ include "nextcloud-mcp-server.fullname" . }}"}[5m]))
|
||||||
|
/ sum(rate(mcp_http_requests_total{job="{{ include "nextcloud-mcp-server.fullname" . }}"}[5m])) > 0.05
|
||||||
|
for: 5m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
annotations:
|
||||||
|
summary: "High error rate on Nextcloud MCP Server"
|
||||||
|
description: "Error rate is {{ `{{` }} printf \"%.2f%%\" (mul $value 100) {{ `}}` }} (threshold: 5%)"
|
||||||
|
|
||||||
|
- alert: NextcloudMCPHighLatency
|
||||||
|
expr: |
|
||||||
|
histogram_quantile(0.95,
|
||||||
|
sum(rate(mcp_http_request_duration_seconds_bucket{job="{{ include "nextcloud-mcp-server.fullname" . }}"}[5m])) by (le, endpoint)
|
||||||
|
) > 1
|
||||||
|
for: 5m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
annotations:
|
||||||
|
summary: "High latency on Nextcloud MCP Server"
|
||||||
|
description: "P95 latency is {{ `{{` }} printf \"%.2fs\" $value {{ `}}` }} on {{ `{{` }} $labels.endpoint {{ `}}` }} (threshold: 1s)"
|
||||||
|
|
||||||
|
- alert: NextcloudMCPDependencyDown
|
||||||
|
expr: mcp_dependency_health{job="{{ include "nextcloud-mcp-server.fullname" . }}"} == 0
|
||||||
|
for: 2m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
annotations:
|
||||||
|
summary: "Nextcloud MCP dependency is down"
|
||||||
|
description: "Dependency {{ `{{` }} $labels.dependency {{ `}}` }} has been down for more than 2 minutes."
|
||||||
|
|
||||||
|
- name: nextcloud-mcp-server.warning
|
||||||
|
interval: 30s
|
||||||
|
rules:
|
||||||
|
- alert: NextcloudMCPTokenValidationErrors
|
||||||
|
expr: |
|
||||||
|
sum(rate(mcp_oauth_token_validations_total{result="error", job="{{ include "nextcloud-mcp-server.fullname" . }}"}[10m]))
|
||||||
|
/ sum(rate(mcp_oauth_token_validations_total{job="{{ include "nextcloud-mcp-server.fullname" . }}"}[10m])) > 0.01
|
||||||
|
for: 10m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "High token validation error rate"
|
||||||
|
description: "Token validation error rate is {{ `{{` }} printf \"%.2f%%\" (mul $value 100) {{ `}}` }} (threshold: 1%)"
|
||||||
|
|
||||||
|
- alert: NextcloudMCPVectorSyncQueueHigh
|
||||||
|
expr: mcp_vector_sync_queue_size{job="{{ include "nextcloud-mcp-server.fullname" . }}"} > 100
|
||||||
|
for: 15m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "Vector sync queue is high"
|
||||||
|
description: "Vector sync queue size is {{ `{{` }} $value {{ `}}` }} (threshold: 100)"
|
||||||
|
|
||||||
|
- alert: NextcloudMCPQdrantSlowQueries
|
||||||
|
expr: |
|
||||||
|
histogram_quantile(0.95,
|
||||||
|
sum(rate(mcp_db_operation_duration_seconds_bucket{db="qdrant", job="{{ include "nextcloud-mcp-server.fullname" . }}"}[10m])) by (le)
|
||||||
|
) > 0.5
|
||||||
|
for: 10m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "Qdrant queries are slow"
|
||||||
|
description: "P95 Qdrant query latency is {{ `{{` }} printf \"%.2fs\" $value {{ `}}` }} (threshold: 0.5s)"
|
||||||
|
{{- end }}
|
||||||
@@ -15,5 +15,11 @@ spec:
|
|||||||
targetPort: http
|
targetPort: http
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
name: http
|
name: http
|
||||||
|
{{- if .Values.observability.metrics.enabled }}
|
||||||
|
- port: {{ .Values.observability.metrics.port }}
|
||||||
|
targetPort: metrics
|
||||||
|
protocol: TCP
|
||||||
|
name: metrics
|
||||||
|
{{- end }}
|
||||||
selector:
|
selector:
|
||||||
{{- include "nextcloud-mcp-server.selectorLabels" . | nindent 4 }}
|
{{- include "nextcloud-mcp-server.selectorLabels" . | nindent 4 }}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{{- if and .Values.observability.metrics.enabled .Values.serviceMonitor.enabled }}
|
||||||
|
apiVersion: monitoring.coreos.com/v1
|
||||||
|
kind: ServiceMonitor
|
||||||
|
metadata:
|
||||||
|
name: {{ include "nextcloud-mcp-server.fullname" . }}
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
{{- include "nextcloud-mcp-server.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.serviceMonitor.labels }}
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "nextcloud-mcp-server.selectorLabels" . | nindent 6 }}
|
||||||
|
endpoints:
|
||||||
|
- port: metrics
|
||||||
|
path: {{ .Values.observability.metrics.path }}
|
||||||
|
interval: {{ .Values.serviceMonitor.interval }}
|
||||||
|
scrapeTimeout: {{ .Values.serviceMonitor.scrapeTimeout }}
|
||||||
|
scheme: http
|
||||||
|
relabelings:
|
||||||
|
# Add namespace label
|
||||||
|
- sourceLabels: [__meta_kubernetes_namespace]
|
||||||
|
targetLabel: namespace
|
||||||
|
# Add pod label
|
||||||
|
- sourceLabels: [__meta_kubernetes_pod_name]
|
||||||
|
targetLabel: pod
|
||||||
|
# Add service label
|
||||||
|
- sourceLabels: [__meta_kubernetes_service_name]
|
||||||
|
targetLabel: service
|
||||||
|
{{- end }}
|
||||||
@@ -168,6 +168,43 @@ securityContext:
|
|||||||
runAsNonRoot: true
|
runAsNonRoot: true
|
||||||
runAsUser: 1000
|
runAsUser: 1000
|
||||||
|
|
||||||
|
# Observability Configuration
|
||||||
|
observability:
|
||||||
|
# Prometheus metrics
|
||||||
|
metrics:
|
||||||
|
enabled: true
|
||||||
|
port: 9090
|
||||||
|
path: /metrics
|
||||||
|
|
||||||
|
# OpenTelemetry tracing
|
||||||
|
tracing:
|
||||||
|
enabled: false
|
||||||
|
endpoint: "" # e.g., "http://opentelemetry-collector:4317"
|
||||||
|
serviceName: "nextcloud-mcp-server"
|
||||||
|
samplingRate: 1.0
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
logging:
|
||||||
|
format: json # "json" or "text"
|
||||||
|
level: INFO
|
||||||
|
includeTraceContext: true
|
||||||
|
|
||||||
|
# Prometheus ServiceMonitor (requires Prometheus Operator)
|
||||||
|
serviceMonitor:
|
||||||
|
enabled: false
|
||||||
|
interval: 30s
|
||||||
|
scrapeTimeout: 10s
|
||||||
|
labels: {}
|
||||||
|
# Additional labels for ServiceMonitor (e.g., for Prometheus selector)
|
||||||
|
# Example: { prometheus: kube-prometheus }
|
||||||
|
|
||||||
|
# Prometheus alert rules (requires Prometheus Operator)
|
||||||
|
prometheusRule:
|
||||||
|
enabled: false
|
||||||
|
labels: {}
|
||||||
|
# Additional labels for PrometheusRule (e.g., for Prometheus selector)
|
||||||
|
# Example: { prometheus: kube-prometheus }
|
||||||
|
|
||||||
service:
|
service:
|
||||||
type: ClusterIP
|
type: ClusterIP
|
||||||
port: 8000
|
port: 8000
|
||||||
|
|||||||
@@ -1172,13 +1172,15 @@ def get_app(transport: str = "sse", enabled_apps: list[str] | None = None):
|
|||||||
checks["auth_configured"] = "error: credentials not set"
|
checks["auth_configured"] = "error: credentials not set"
|
||||||
is_ready = False
|
is_ready = False
|
||||||
|
|
||||||
# Check Qdrant status if vector sync is enabled
|
# Check Qdrant status if using network mode (external Qdrant service)
|
||||||
|
# In-memory and persistent modes use embedded Qdrant, no external service to check
|
||||||
vector_sync_enabled = (
|
vector_sync_enabled = (
|
||||||
os.getenv("VECTOR_SYNC_ENABLED", "false").lower() == "true"
|
os.getenv("VECTOR_SYNC_ENABLED", "false").lower() == "true"
|
||||||
)
|
)
|
||||||
if vector_sync_enabled:
|
qdrant_url = os.getenv("QDRANT_URL") # Only set in network mode
|
||||||
|
|
||||||
|
if vector_sync_enabled and qdrant_url:
|
||||||
try:
|
try:
|
||||||
qdrant_url = os.getenv("QDRANT_URL", "http://qdrant:6333")
|
|
||||||
async with httpx.AsyncClient(timeout=2.0) as client:
|
async with httpx.AsyncClient(timeout=2.0) as client:
|
||||||
response = await client.get(f"{qdrant_url}/readyz")
|
response = await client.get(f"{qdrant_url}/readyz")
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
@@ -1189,6 +1191,9 @@ def get_app(transport: str = "sse", enabled_apps: list[str] | None = None):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
checks["qdrant"] = f"error: {str(e)}"
|
checks["qdrant"] = f"error: {str(e)}"
|
||||||
is_ready = False
|
is_ready = False
|
||||||
|
elif vector_sync_enabled:
|
||||||
|
# Using embedded Qdrant (memory or persistent mode)
|
||||||
|
checks["qdrant"] = "embedded"
|
||||||
|
|
||||||
status_code = 200 if is_ready else 503
|
status_code = 200 if is_ready else 503
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "nextcloud-mcp-server"
|
name = "nextcloud-mcp-server"
|
||||||
version = "0.28.0"
|
version = "0.29.0"
|
||||||
description = "Model Context Protocol (MCP) server for Nextcloud integration - enables AI assistants to interact with Nextcloud data"
|
description = "Model Context Protocol (MCP) server for Nextcloud integration - enables AI assistants to interact with Nextcloud data"
|
||||||
authors = [
|
authors = [
|
||||||
{name = "Chris Coutinho", email = "chris@coutinho.io"}
|
{name = "Chris Coutinho", email = "chris@coutinho.io"}
|
||||||
|
|||||||
@@ -1059,7 +1059,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nextcloud-mcp-server"
|
name = "nextcloud-mcp-server"
|
||||||
version = "0.28.0"
|
version = "0.29.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "aiosqlite" },
|
{ name = "aiosqlite" },
|
||||||
|
|||||||
Reference in New Issue
Block a user