@anyshift/mcp-proxy 0.3.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/unit/timeseries.test.d.ts +1 -0
- package/dist/__tests__/unit/timeseries.test.js +217 -0
- package/dist/fileWriter/writer.js +1 -1
- package/dist/index.js +53 -2
- package/dist/jq/handler.js +11 -56
- package/dist/timeseries/algorithms/index.d.ts +8 -0
- package/dist/timeseries/algorithms/index.js +8 -0
- package/dist/timeseries/algorithms/mad.d.ts +15 -0
- package/dist/timeseries/algorithms/mad.js +44 -0
- package/dist/timeseries/algorithms/moving-average.d.ts +15 -0
- package/dist/timeseries/algorithms/moving-average.js +72 -0
- package/dist/timeseries/algorithms/rolling-quantile.d.ts +16 -0
- package/dist/timeseries/algorithms/rolling-quantile.js +78 -0
- package/dist/timeseries/algorithms/stats.d.ts +49 -0
- package/dist/timeseries/algorithms/stats.js +139 -0
- package/dist/timeseries/algorithms/threshold.d.ts +15 -0
- package/dist/timeseries/algorithms/threshold.js +49 -0
- package/dist/timeseries/handler.d.ts +10 -0
- package/dist/timeseries/handler.js +292 -0
- package/dist/timeseries/index.d.ts +68 -0
- package/dist/timeseries/index.js +26 -0
- package/dist/timeseries/tool.d.ts +71 -0
- package/dist/timeseries/tool.js +170 -0
- package/dist/timeseries/types.d.ts +147 -0
- package/dist/timeseries/types.js +4 -0
- package/dist/utils/jq.d.ts +25 -0
- package/dist/utils/jq.js +90 -0
- package/package.json +1 -1
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { TimeseriesConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Create a timeseries anomaly detection tool instance with the given configuration
|
|
4
|
+
* @param config - Timeseries configuration
|
|
5
|
+
* @returns Object with handler and toolDefinition
|
|
6
|
+
*/
|
|
7
|
+
export declare function createTimeseriesTool(config: TimeseriesConfig): {
|
|
8
|
+
/**
|
|
9
|
+
* Tool definition for MCP server registration
|
|
10
|
+
*/
|
|
11
|
+
toolDefinition: {
|
|
12
|
+
name: string;
|
|
13
|
+
description: string;
|
|
14
|
+
inputSchema: {
|
|
15
|
+
type: string;
|
|
16
|
+
properties: {
|
|
17
|
+
file_path: {
|
|
18
|
+
type: string;
|
|
19
|
+
description: string;
|
|
20
|
+
};
|
|
21
|
+
jq_query: {
|
|
22
|
+
type: string;
|
|
23
|
+
description: string;
|
|
24
|
+
};
|
|
25
|
+
timestamp_field: {
|
|
26
|
+
type: string;
|
|
27
|
+
description: string;
|
|
28
|
+
};
|
|
29
|
+
value_field: {
|
|
30
|
+
type: string;
|
|
31
|
+
description: string;
|
|
32
|
+
};
|
|
33
|
+
detection_type: {
|
|
34
|
+
type: string;
|
|
35
|
+
enum: string[];
|
|
36
|
+
description: string;
|
|
37
|
+
};
|
|
38
|
+
detection_params: {
|
|
39
|
+
type: string;
|
|
40
|
+
description: string;
|
|
41
|
+
};
|
|
42
|
+
description: {
|
|
43
|
+
type: string;
|
|
44
|
+
description: string;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
required: string[];
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Handler for timeseries anomaly detection requests
|
|
52
|
+
* @param request - MCP request containing parameters
|
|
53
|
+
* @returns Promise with the detection result
|
|
54
|
+
*/
|
|
55
|
+
handler: (request: {
|
|
56
|
+
params: {
|
|
57
|
+
arguments: Record<string, unknown>;
|
|
58
|
+
};
|
|
59
|
+
}) => Promise<{
|
|
60
|
+
content: Array<{
|
|
61
|
+
type: "text";
|
|
62
|
+
text: string;
|
|
63
|
+
}>;
|
|
64
|
+
}>;
|
|
65
|
+
};
|
|
66
|
+
export type { TimeseriesConfig, DataPoint, Anomaly, AnomalyDetectionResult, DetectionType, DetectionParams, NamedThreshold, ThresholdDirection, } from './types.js';
|
|
67
|
+
export { TimeseriesAnomalySchema, TIMESERIES_ANOMALY_TOOL_DEFINITION } from './tool.js';
|
|
68
|
+
export { detectTimeseriesAnomalies } from './handler.js';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { detectTimeseriesAnomalies } from './handler.js';
|
|
2
|
+
import { TimeseriesAnomalySchema, TIMESERIES_ANOMALY_TOOL_DEFINITION } from './tool.js';
|
|
3
|
+
/**
|
|
4
|
+
* Create a timeseries anomaly detection tool instance with the given configuration
|
|
5
|
+
* @param config - Timeseries configuration
|
|
6
|
+
* @returns Object with handler and toolDefinition
|
|
7
|
+
*/
|
|
8
|
+
export function createTimeseriesTool(config) {
|
|
9
|
+
return {
|
|
10
|
+
/**
|
|
11
|
+
* Tool definition for MCP server registration
|
|
12
|
+
*/
|
|
13
|
+
toolDefinition: TIMESERIES_ANOMALY_TOOL_DEFINITION,
|
|
14
|
+
/**
|
|
15
|
+
* Handler for timeseries anomaly detection requests
|
|
16
|
+
* @param request - MCP request containing parameters
|
|
17
|
+
* @returns Promise with the detection result
|
|
18
|
+
*/
|
|
19
|
+
handler: async (request) => {
|
|
20
|
+
const parsed = TimeseriesAnomalySchema.parse(request.params.arguments);
|
|
21
|
+
return detectTimeseriesAnomalies(config, parsed.file_path, parsed.jq_query, parsed.value_field, parsed.detection_type, parsed.detection_params, parsed.timestamp_field);
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export { TimeseriesAnomalySchema, TIMESERIES_ANOMALY_TOOL_DEFINITION } from './tool.js';
|
|
26
|
+
export { detectTimeseriesAnomalies } from './handler.js';
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Zod schema for time series anomaly detection
|
|
4
|
+
*/
|
|
5
|
+
export declare const TimeseriesAnomalySchema: z.ZodObject<{
|
|
6
|
+
file_path: z.ZodString;
|
|
7
|
+
jq_query: z.ZodString;
|
|
8
|
+
timestamp_field: z.ZodOptional<z.ZodString>;
|
|
9
|
+
value_field: z.ZodString;
|
|
10
|
+
detection_type: z.ZodEnum<["rolling_quantile", "mad", "moving_average", "threshold"]>;
|
|
11
|
+
detection_params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
12
|
+
description: z.ZodOptional<z.ZodString>;
|
|
13
|
+
}, "strip", z.ZodTypeAny, {
|
|
14
|
+
jq_query: string;
|
|
15
|
+
file_path: string;
|
|
16
|
+
value_field: string;
|
|
17
|
+
detection_type: "rolling_quantile" | "mad" | "moving_average" | "threshold";
|
|
18
|
+
timestamp_field?: string | undefined;
|
|
19
|
+
detection_params?: Record<string, unknown> | undefined;
|
|
20
|
+
description?: string | undefined;
|
|
21
|
+
}, {
|
|
22
|
+
jq_query: string;
|
|
23
|
+
file_path: string;
|
|
24
|
+
value_field: string;
|
|
25
|
+
detection_type: "rolling_quantile" | "mad" | "moving_average" | "threshold";
|
|
26
|
+
timestamp_field?: string | undefined;
|
|
27
|
+
detection_params?: Record<string, unknown> | undefined;
|
|
28
|
+
description?: string | undefined;
|
|
29
|
+
}>;
|
|
30
|
+
/**
|
|
31
|
+
* Tool definition for time series anomaly detection with comprehensive documentation
|
|
32
|
+
*/
|
|
33
|
+
export declare const TIMESERIES_ANOMALY_TOOL_DEFINITION: {
|
|
34
|
+
name: string;
|
|
35
|
+
description: string;
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: string;
|
|
38
|
+
properties: {
|
|
39
|
+
file_path: {
|
|
40
|
+
type: string;
|
|
41
|
+
description: string;
|
|
42
|
+
};
|
|
43
|
+
jq_query: {
|
|
44
|
+
type: string;
|
|
45
|
+
description: string;
|
|
46
|
+
};
|
|
47
|
+
timestamp_field: {
|
|
48
|
+
type: string;
|
|
49
|
+
description: string;
|
|
50
|
+
};
|
|
51
|
+
value_field: {
|
|
52
|
+
type: string;
|
|
53
|
+
description: string;
|
|
54
|
+
};
|
|
55
|
+
detection_type: {
|
|
56
|
+
type: string;
|
|
57
|
+
enum: string[];
|
|
58
|
+
description: string;
|
|
59
|
+
};
|
|
60
|
+
detection_params: {
|
|
61
|
+
type: string;
|
|
62
|
+
description: string;
|
|
63
|
+
};
|
|
64
|
+
description: {
|
|
65
|
+
type: string;
|
|
66
|
+
description: string;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
required: string[];
|
|
70
|
+
};
|
|
71
|
+
};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Schema for a named threshold
|
|
4
|
+
*/
|
|
5
|
+
const NamedThresholdSchema = z.object({
|
|
6
|
+
name: z.string().describe('Name/label for this threshold (e.g., "critical", "warning", "min_healthy")'),
|
|
7
|
+
value: z.number().describe('The threshold value'),
|
|
8
|
+
direction: z.enum(['upper', 'lower']).describe('Whether values should be below (upper bound) or above (lower bound) this threshold'),
|
|
9
|
+
});
|
|
10
|
+
/**
|
|
11
|
+
* Zod schema for time series anomaly detection
|
|
12
|
+
*/
|
|
13
|
+
export const TimeseriesAnomalySchema = z.object({
|
|
14
|
+
file_path: z
|
|
15
|
+
.string()
|
|
16
|
+
.describe('Absolute path starting with "/" pointing to the JSON file to process. Must be a valid, existing file with .json extension.'),
|
|
17
|
+
jq_query: z
|
|
18
|
+
.string()
|
|
19
|
+
.describe('JQ query to extract tabular data from the JSON file. The query must return an array of objects with consistent fields. Use PLAIN quotes like .["field"] - DO NOT escape them.'),
|
|
20
|
+
timestamp_field: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe('Optional field name in the extracted data to use as timestamp. If provided, timestamps will be included in anomaly results.'),
|
|
24
|
+
value_field: z
|
|
25
|
+
.string()
|
|
26
|
+
.describe('Field name in the extracted data containing the numeric values to analyze for anomalies.'),
|
|
27
|
+
detection_type: z
|
|
28
|
+
.enum(['rolling_quantile', 'mad', 'moving_average', 'threshold'])
|
|
29
|
+
.describe('The anomaly detection algorithm to use. See algorithm descriptions below.'),
|
|
30
|
+
detection_params: z
|
|
31
|
+
.record(z.unknown())
|
|
32
|
+
.optional()
|
|
33
|
+
.describe('Algorithm-specific parameters. See parameter descriptions for each detection_type.'),
|
|
34
|
+
description: z
|
|
35
|
+
.string()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe('Brief explanation of why you are calling this tool and what you expect to learn/achieve.'),
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* Tool definition for time series anomaly detection with comprehensive documentation
|
|
41
|
+
*/
|
|
42
|
+
export const TIMESERIES_ANOMALY_TOOL_DEFINITION = {
|
|
43
|
+
name: 'detect_timeseries_anomalies',
|
|
44
|
+
description: 'Detect anomalies in time series data extracted from JSON files using jq queries. ' +
|
|
45
|
+
'This tool combines jq-based data extraction with statistical anomaly detection algorithms.' +
|
|
46
|
+
'\n\n## IMPORTANT: This tool has jq built-in. Do NOT prepare or transform data separately.' +
|
|
47
|
+
'\n- NO separate "transform data" step needed' +
|
|
48
|
+
'\n- NO calling execute_jq_query first' +
|
|
49
|
+
'\n- The jq_query parameter handles ALL data extraction and transformation in ONE call' +
|
|
50
|
+
'\n\n## WHAT THIS TOOL DOES (automatically, in a single call):' +
|
|
51
|
+
'\n1. Reads the JSON file' +
|
|
52
|
+
'\n2. Applies the jq query to extract tabular data (array of objects)' +
|
|
53
|
+
'\n3. Extracts numeric values from the specified value_field' +
|
|
54
|
+
'\n4. Runs the selected anomaly detection algorithm' +
|
|
55
|
+
'\n5. Returns detected anomalies with indices, values, and reasons' +
|
|
56
|
+
'\n\n## DATA REQUIREMENTS:' +
|
|
57
|
+
'\n- The jq_query MUST return an array of objects: [{...}, {...}, ...]' +
|
|
58
|
+
'\n- Each object MUST have the field specified in value_field' +
|
|
59
|
+
'\n- The value_field MUST contain numeric values' +
|
|
60
|
+
'\n- If timestamp_field is provided, it will be included in results' +
|
|
61
|
+
'\n\n## DETECTION ALGORITHMS:' +
|
|
62
|
+
'\n\n### rolling_quantile (RECOMMENDED - similar to Datadog Basic)' +
|
|
63
|
+
'\nUses a sliding window to compute rolling percentiles, flags points outside the expected range.' +
|
|
64
|
+
'\n- Best for: General purpose, works with any data distribution' +
|
|
65
|
+
'\n- Parameters: { "window_size": 10, "lower_quantile": 0.05, "upper_quantile": 0.95 }' +
|
|
66
|
+
'\n- REQUIRED: window_size (number of points in rolling window)' +
|
|
67
|
+
'\n- lower_quantile: percentile for lower bound (default: 0.05 = 5th percentile)' +
|
|
68
|
+
'\n- upper_quantile: percentile for upper bound (default: 0.95 = 95th percentile)' +
|
|
69
|
+
'\n- Non-parametric: does not assume normal distribution' +
|
|
70
|
+
'\n\n### mad (Median Absolute Deviation)' +
|
|
71
|
+
'\nUses modified Z-scores based on median instead of mean.' +
|
|
72
|
+
'\n- Best for: Global outlier detection across all data, robust to existing outliers' +
|
|
73
|
+
'\n- Parameters: { "threshold": 3 } (default: 3)' +
|
|
74
|
+
'\n- Uses median-based statistics for maximum robustness' +
|
|
75
|
+
'\n- Good when you want to find outliers relative to the entire dataset' +
|
|
76
|
+
'\n\n### moving_average' +
|
|
77
|
+
'\nDetects points that deviate from a rolling average.' +
|
|
78
|
+
'\n- Best for: Time series with trends, detecting sudden changes' +
|
|
79
|
+
'\n- Parameters: { "window_size": 10, "threshold": 2 }' +
|
|
80
|
+
'\n- REQUIRED: window_size (number of points in rolling window)' +
|
|
81
|
+
'\n- threshold: std devs from moving average (default: 2)' +
|
|
82
|
+
'\n- Assumes roughly normal distribution within each window' +
|
|
83
|
+
'\n\n### threshold' +
|
|
84
|
+
'\nDetects points crossing named upper/lower thresholds.' +
|
|
85
|
+
'\n- Best for: SLA monitoring, known limits' +
|
|
86
|
+
'\n- Parameters: { "thresholds": [...] }' +
|
|
87
|
+
'\n- REQUIRED: thresholds array with named thresholds' +
|
|
88
|
+
'\n- Each threshold: { "name": "critical", "value": 100, "direction": "upper" }' +
|
|
89
|
+
'\n- direction: "upper" = value must be below, "lower" = value must be above' +
|
|
90
|
+
'\n\n## JQ QUERY TIPS:' +
|
|
91
|
+
'\n- Use PLAIN quotes: .["field"] NOT .[\"field\"]' +
|
|
92
|
+
'\n- Array of metrics: .metrics' +
|
|
93
|
+
'\n- Nested data: .data.timeseries' +
|
|
94
|
+
'\n- Filter: .items[] | select(.type == "cpu") | {ts: .timestamp, val: .value}' +
|
|
95
|
+
'\n- Transform: [.[] | {timestamp: .ts, value: .cpu_usage}]' +
|
|
96
|
+
'\n\n## EXAMPLE CALLS:' +
|
|
97
|
+
'\n\n**Rolling Quantile detection (RECOMMENDED):**' +
|
|
98
|
+
'\n```json' +
|
|
99
|
+
'\n{' +
|
|
100
|
+
'\n "file_path": "/tmp/metrics.json",' +
|
|
101
|
+
'\n "jq_query": ".cpu_metrics",' +
|
|
102
|
+
'\n "value_field": "usage",' +
|
|
103
|
+
'\n "timestamp_field": "timestamp",' +
|
|
104
|
+
'\n "detection_type": "rolling_quantile",' +
|
|
105
|
+
'\n "detection_params": { "window_size": 10, "lower_quantile": 0.05, "upper_quantile": 0.95 }' +
|
|
106
|
+
'\n}' +
|
|
107
|
+
'\n```' +
|
|
108
|
+
'\n\n**Threshold detection with SLA limits:**' +
|
|
109
|
+
'\n```json' +
|
|
110
|
+
'\n{' +
|
|
111
|
+
'\n "file_path": "/tmp/response_times.json",' +
|
|
112
|
+
'\n "jq_query": ".api_latencies",' +
|
|
113
|
+
'\n "value_field": "latency_ms",' +
|
|
114
|
+
'\n "detection_type": "threshold",' +
|
|
115
|
+
'\n "detection_params": {' +
|
|
116
|
+
'\n "thresholds": [' +
|
|
117
|
+
'\n { "name": "warning", "value": 200, "direction": "upper" },' +
|
|
118
|
+
'\n { "name": "critical", "value": 500, "direction": "upper" },' +
|
|
119
|
+
'\n { "name": "too_fast", "value": 1, "direction": "lower" }' +
|
|
120
|
+
'\n ]' +
|
|
121
|
+
'\n }' +
|
|
122
|
+
'\n}' +
|
|
123
|
+
'\n```' +
|
|
124
|
+
'\n\n**MAD for global outlier detection:**' +
|
|
125
|
+
'\n```json' +
|
|
126
|
+
'\n{' +
|
|
127
|
+
'\n "file_path": "/tmp/sales.json",' +
|
|
128
|
+
'\n "jq_query": "[.daily_sales[] | {date: .date, amount: .total}]",' +
|
|
129
|
+
'\n "value_field": "amount",' +
|
|
130
|
+
'\n "timestamp_field": "date",' +
|
|
131
|
+
'\n "detection_type": "mad",' +
|
|
132
|
+
'\n "detection_params": { "threshold": 3 }' +
|
|
133
|
+
'\n}' +
|
|
134
|
+
'\n```',
|
|
135
|
+
inputSchema: {
|
|
136
|
+
type: 'object',
|
|
137
|
+
properties: {
|
|
138
|
+
file_path: {
|
|
139
|
+
type: 'string',
|
|
140
|
+
description: 'Absolute path starting with "/" pointing to the JSON file to process. Must be a valid, existing file with .json extension.',
|
|
141
|
+
},
|
|
142
|
+
jq_query: {
|
|
143
|
+
type: 'string',
|
|
144
|
+
description: 'JQ query to extract tabular data from the JSON file. The query must return an array of objects with consistent fields. Use PLAIN quotes like .["field"] - DO NOT escape them.',
|
|
145
|
+
},
|
|
146
|
+
timestamp_field: {
|
|
147
|
+
type: 'string',
|
|
148
|
+
description: 'Optional field name in the extracted data to use as timestamp. If provided, timestamps will be included in anomaly results.',
|
|
149
|
+
},
|
|
150
|
+
value_field: {
|
|
151
|
+
type: 'string',
|
|
152
|
+
description: 'Field name in the extracted data containing the numeric values to analyze for anomalies.',
|
|
153
|
+
},
|
|
154
|
+
detection_type: {
|
|
155
|
+
type: 'string',
|
|
156
|
+
enum: ['rolling_quantile', 'mad', 'moving_average', 'threshold'],
|
|
157
|
+
description: 'The anomaly detection algorithm to use: rolling_quantile (recommended), mad, moving_average, or threshold.',
|
|
158
|
+
},
|
|
159
|
+
detection_params: {
|
|
160
|
+
type: 'object',
|
|
161
|
+
description: 'Algorithm-specific parameters. For rolling_quantile: {window_size: number, lower_quantile?: number, upper_quantile?: number}. For mad: {threshold: number}. For moving_average: {window_size: number, threshold?: number}. For threshold: {thresholds: [{name, value, direction}]}.',
|
|
162
|
+
},
|
|
163
|
+
description: {
|
|
164
|
+
type: 'string',
|
|
165
|
+
description: 'Brief explanation of why you are calling this tool and what you expect to learn/achieve.',
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
required: ['file_path', 'jq_query', 'value_field', 'detection_type'],
|
|
169
|
+
},
|
|
170
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time Series Anomaly Detection Types
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* A single data point in the time series
|
|
6
|
+
*/
|
|
7
|
+
export interface DataPoint {
|
|
8
|
+
/** Index in the original array */
|
|
9
|
+
index: number;
|
|
10
|
+
/** Timestamp value (if available) */
|
|
11
|
+
timestamp?: string | number;
|
|
12
|
+
/** The numeric value */
|
|
13
|
+
value: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Threshold direction for threshold-based detection
|
|
17
|
+
*/
|
|
18
|
+
export type ThresholdDirection = 'upper' | 'lower';
|
|
19
|
+
/**
|
|
20
|
+
* A named threshold with direction
|
|
21
|
+
*/
|
|
22
|
+
export interface NamedThreshold {
|
|
23
|
+
/** Name/label for this threshold */
|
|
24
|
+
name: string;
|
|
25
|
+
/** The threshold value */
|
|
26
|
+
value: number;
|
|
27
|
+
/** Whether this is an upper or lower bound */
|
|
28
|
+
direction: ThresholdDirection;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Detection types supported by the tool
|
|
32
|
+
*/
|
|
33
|
+
export type DetectionType = 'rolling_quantile' | 'mad' | 'moving_average' | 'threshold';
|
|
34
|
+
/**
|
|
35
|
+
* Parameters for Rolling Quantile detection (similar to Datadog Basic)
|
|
36
|
+
*/
|
|
37
|
+
export interface RollingQuantileParams {
|
|
38
|
+
/** Window size for the rolling quantile calculation */
|
|
39
|
+
window_size: number;
|
|
40
|
+
/** Lower quantile for the expected range (default: 0.05 = 5th percentile) */
|
|
41
|
+
lower_quantile?: number;
|
|
42
|
+
/** Upper quantile for the expected range (default: 0.95 = 95th percentile) */
|
|
43
|
+
upper_quantile?: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Parameters for MAD (Median Absolute Deviation) detection
|
|
47
|
+
*/
|
|
48
|
+
export interface MadParams {
|
|
49
|
+
/** Multiplier for MAD threshold (default: 3) */
|
|
50
|
+
threshold?: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Parameters for Moving Average detection
|
|
54
|
+
*/
|
|
55
|
+
export interface MovingAverageParams {
|
|
56
|
+
/** Window size for the moving average */
|
|
57
|
+
window_size: number;
|
|
58
|
+
/** Number of standard deviations from moving average (default: 2) */
|
|
59
|
+
threshold?: number;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Parameters for Threshold detection
|
|
63
|
+
*/
|
|
64
|
+
export interface ThresholdParams {
|
|
65
|
+
/** Named thresholds with direction */
|
|
66
|
+
thresholds: NamedThreshold[];
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Union type for all detection parameters
|
|
70
|
+
*/
|
|
71
|
+
export type DetectionParams = RollingQuantileParams | MadParams | MovingAverageParams | ThresholdParams;
|
|
72
|
+
/**
|
|
73
|
+
* A detected anomaly (individual point)
|
|
74
|
+
*/
|
|
75
|
+
export interface Anomaly {
|
|
76
|
+
/** Index in the original data */
|
|
77
|
+
index: number;
|
|
78
|
+
/** Timestamp if available (raw value from data) */
|
|
79
|
+
timestamp?: string | number;
|
|
80
|
+
/** UTC time string if timestamp was a Unix timestamp */
|
|
81
|
+
utc_time?: string;
|
|
82
|
+
/** The anomalous value */
|
|
83
|
+
value: number;
|
|
84
|
+
/** Reason/description of why this is an anomaly */
|
|
85
|
+
reason: string;
|
|
86
|
+
/** For threshold detection: which thresholds were crossed */
|
|
87
|
+
crossed_thresholds?: string[];
|
|
88
|
+
/** Additional metadata (e.g., z-score value, percent change) */
|
|
89
|
+
metadata?: Record<string, number>;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A pooled anomaly (consecutive anomalies grouped together)
|
|
93
|
+
*/
|
|
94
|
+
export interface PooledAnomaly {
|
|
95
|
+
/** Start index in the original data */
|
|
96
|
+
start_index: number;
|
|
97
|
+
/** End index in the original data (inclusive) */
|
|
98
|
+
end_index: number;
|
|
99
|
+
/** Number of anomalous points in this pool */
|
|
100
|
+
count: number;
|
|
101
|
+
/** Start timestamp if available */
|
|
102
|
+
start_timestamp?: string | number;
|
|
103
|
+
/** End timestamp if available */
|
|
104
|
+
end_timestamp?: string | number;
|
|
105
|
+
/** Start UTC time string */
|
|
106
|
+
start_utc?: string;
|
|
107
|
+
/** End UTC time string */
|
|
108
|
+
end_utc?: string;
|
|
109
|
+
/** Minimum value in the pool */
|
|
110
|
+
min_value: number;
|
|
111
|
+
/** Maximum value in the pool */
|
|
112
|
+
max_value: number;
|
|
113
|
+
/** Average value in the pool */
|
|
114
|
+
avg_value: number;
|
|
115
|
+
/** Summary reason for the pool */
|
|
116
|
+
reason: string;
|
|
117
|
+
/** For threshold detection: union of all crossed thresholds */
|
|
118
|
+
crossed_thresholds?: string[];
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Result of anomaly detection
|
|
122
|
+
*/
|
|
123
|
+
export interface AnomalyDetectionResult {
|
|
124
|
+
/** Total number of data points analyzed */
|
|
125
|
+
total_points: number;
|
|
126
|
+
/** Number of anomalous points detected */
|
|
127
|
+
anomaly_count: number;
|
|
128
|
+
/** Percentage of points that are anomalies */
|
|
129
|
+
anomaly_percentage: number;
|
|
130
|
+
/** Number of anomaly pools (consecutive anomalies grouped) */
|
|
131
|
+
pool_count: number;
|
|
132
|
+
/** List of pooled anomalies (consecutive anomalies grouped together) */
|
|
133
|
+
anomalies: PooledAnomaly[];
|
|
134
|
+
/** Detection method used */
|
|
135
|
+
detection_type: DetectionType;
|
|
136
|
+
/** Parameters used for detection */
|
|
137
|
+
parameters: DetectionParams;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Configuration for the timeseries anomaly detection tool
|
|
141
|
+
*/
|
|
142
|
+
export interface TimeseriesConfig {
|
|
143
|
+
/** Paths where the tool is allowed to read files */
|
|
144
|
+
allowedPaths: string[];
|
|
145
|
+
/** Timeout for jq query execution in milliseconds (default: 30000) */
|
|
146
|
+
timeoutMs?: number;
|
|
147
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dangerous patterns that could access environment variables or system info in jq queries
|
|
3
|
+
*/
|
|
4
|
+
export declare const DANGEROUS_JQ_PATTERNS: RegExp[];
|
|
5
|
+
/**
|
|
6
|
+
* Validate a jq query for dangerous patterns
|
|
7
|
+
* @throws Error if the query contains dangerous patterns
|
|
8
|
+
*/
|
|
9
|
+
export declare function validateJqQuery(jqQuery: string): void;
|
|
10
|
+
/**
|
|
11
|
+
* Execute a jq query and return the raw stdout string
|
|
12
|
+
* @param jqQuery - The jq query to execute
|
|
13
|
+
* @param filePath - Path to the JSON file
|
|
14
|
+
* @param timeoutMs - Timeout in milliseconds
|
|
15
|
+
* @returns Raw stdout string from jq
|
|
16
|
+
*/
|
|
17
|
+
export declare function runJq(jqQuery: string, filePath: string, timeoutMs: number): Promise<string>;
|
|
18
|
+
/**
|
|
19
|
+
* Execute a jq query and return the parsed JSON result
|
|
20
|
+
* @param jqQuery - The jq query to execute
|
|
21
|
+
* @param filePath - Path to the JSON file
|
|
22
|
+
* @param timeoutMs - Timeout in milliseconds
|
|
23
|
+
* @returns Parsed JSON result
|
|
24
|
+
*/
|
|
25
|
+
export declare function runJqParsed(jqQuery: string, filePath: string, timeoutMs: number): Promise<unknown>;
|
package/dist/utils/jq.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Dangerous patterns that could access environment variables or system info in jq queries
|
|
4
|
+
*/
|
|
5
|
+
export const DANGEROUS_JQ_PATTERNS = [
|
|
6
|
+
/\$ENV/i, // $ENV variable access
|
|
7
|
+
/env\./i, // env.VARIABLE access
|
|
8
|
+
/@env/i, // @env function
|
|
9
|
+
/\.env\[/i, // .env["VARIABLE"] access
|
|
10
|
+
/getenv/i, // getenv function
|
|
11
|
+
/\$__loc__/i, // location info that might leak paths
|
|
12
|
+
/input_filename/i, // input filename access
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* Validate a jq query for dangerous patterns
|
|
16
|
+
* @throws Error if the query contains dangerous patterns
|
|
17
|
+
*/
|
|
18
|
+
export function validateJqQuery(jqQuery) {
|
|
19
|
+
const isDangerous = DANGEROUS_JQ_PATTERNS.some((pattern) => pattern.test(jqQuery));
|
|
20
|
+
if (isDangerous) {
|
|
21
|
+
throw new Error('The jq query contains patterns that could access environment variables or system information. Please use a different query.');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Execute a jq query and return the raw stdout string
|
|
26
|
+
* @param jqQuery - The jq query to execute
|
|
27
|
+
* @param filePath - Path to the JSON file
|
|
28
|
+
* @param timeoutMs - Timeout in milliseconds
|
|
29
|
+
* @returns Raw stdout string from jq
|
|
30
|
+
*/
|
|
31
|
+
export async function runJq(jqQuery, filePath, timeoutMs) {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
let settled = false;
|
|
34
|
+
const jqProcess = spawn('jq', [jqQuery, filePath], {
|
|
35
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
36
|
+
timeout: timeoutMs,
|
|
37
|
+
});
|
|
38
|
+
let stdout = '';
|
|
39
|
+
let stderr = '';
|
|
40
|
+
jqProcess.stdout.on('data', (data) => {
|
|
41
|
+
stdout += data.toString();
|
|
42
|
+
});
|
|
43
|
+
jqProcess.stderr.on('data', (data) => {
|
|
44
|
+
stderr += data.toString();
|
|
45
|
+
});
|
|
46
|
+
// Handle timeout
|
|
47
|
+
const timeoutId = setTimeout(() => {
|
|
48
|
+
if (!settled && !jqProcess.killed) {
|
|
49
|
+
settled = true;
|
|
50
|
+
jqProcess.kill('SIGTERM');
|
|
51
|
+
reject(new Error(`jq command timed out after ${timeoutMs}ms`));
|
|
52
|
+
}
|
|
53
|
+
}, timeoutMs);
|
|
54
|
+
jqProcess.on('close', (code) => {
|
|
55
|
+
clearTimeout(timeoutId);
|
|
56
|
+
if (settled)
|
|
57
|
+
return;
|
|
58
|
+
settled = true;
|
|
59
|
+
if (code === 0) {
|
|
60
|
+
resolve(stdout.trim());
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
reject(new Error(`jq command failed with exit code ${code}: ${stderr.trim()}`));
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
jqProcess.on('error', (error) => {
|
|
67
|
+
clearTimeout(timeoutId);
|
|
68
|
+
if (settled)
|
|
69
|
+
return;
|
|
70
|
+
settled = true;
|
|
71
|
+
reject(new Error(`Failed to execute jq command: ${error.message}`));
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Execute a jq query and return the parsed JSON result
|
|
77
|
+
* @param jqQuery - The jq query to execute
|
|
78
|
+
* @param filePath - Path to the JSON file
|
|
79
|
+
* @param timeoutMs - Timeout in milliseconds
|
|
80
|
+
* @returns Parsed JSON result
|
|
81
|
+
*/
|
|
82
|
+
export async function runJqParsed(jqQuery, filePath, timeoutMs) {
|
|
83
|
+
const stdout = await runJq(jqQuery, filePath, timeoutMs);
|
|
84
|
+
try {
|
|
85
|
+
return JSON.parse(stdout);
|
|
86
|
+
}
|
|
87
|
+
catch (parseError) {
|
|
88
|
+
throw new Error(`Failed to parse jq output as JSON: ${stdout.slice(0, 200)}`);
|
|
89
|
+
}
|
|
90
|
+
}
|