@anyshift/mcp-proxy 0.3.5 → 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.
Files changed (33) hide show
  1. package/dist/__tests__/unit/timeseries.test.d.ts +1 -0
  2. package/dist/__tests__/unit/timeseries.test.js +217 -0
  3. package/dist/fileWriter/writer.js +1 -1
  4. package/dist/index.js +75 -29
  5. package/dist/jq/handler.js +11 -56
  6. package/dist/jq/tool.d.ts +3 -9
  7. package/dist/jq/tool.js +4 -4
  8. package/dist/timeseries/algorithms/index.d.ts +8 -0
  9. package/dist/timeseries/algorithms/index.js +8 -0
  10. package/dist/timeseries/algorithms/mad.d.ts +15 -0
  11. package/dist/timeseries/algorithms/mad.js +44 -0
  12. package/dist/timeseries/algorithms/moving-average.d.ts +15 -0
  13. package/dist/timeseries/algorithms/moving-average.js +72 -0
  14. package/dist/timeseries/algorithms/rolling-quantile.d.ts +16 -0
  15. package/dist/timeseries/algorithms/rolling-quantile.js +78 -0
  16. package/dist/timeseries/algorithms/stats.d.ts +49 -0
  17. package/dist/timeseries/algorithms/stats.js +139 -0
  18. package/dist/timeseries/algorithms/threshold.d.ts +15 -0
  19. package/dist/timeseries/algorithms/threshold.js +49 -0
  20. package/dist/timeseries/handler.d.ts +10 -0
  21. package/dist/timeseries/handler.js +292 -0
  22. package/dist/timeseries/index.d.ts +68 -0
  23. package/dist/timeseries/index.js +26 -0
  24. package/dist/timeseries/tool.d.ts +71 -0
  25. package/dist/timeseries/tool.js +170 -0
  26. package/dist/timeseries/types.d.ts +147 -0
  27. package/dist/timeseries/types.js +4 -0
  28. package/dist/types/index.d.ts +0 -21
  29. package/dist/utils/filename.d.ts +0 -8
  30. package/dist/utils/filename.js +0 -10
  31. package/dist/utils/jq.d.ts +25 -0
  32. package/dist/utils/jq.js +90 -0
  33. package/package.json +1 -1
@@ -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,4 @@
1
+ /**
2
+ * Time Series Anomaly Detection Types
3
+ */
4
+ export {};
@@ -53,27 +53,6 @@ export interface FileWriterResult {
53
53
  text: string;
54
54
  }>;
55
55
  }
56
- /**
57
- * Schema analysis result for a JSON structure
58
- */
59
- export interface JsonSchema {
60
- type: string;
61
- properties?: Record<string, unknown>;
62
- items?: unknown;
63
- length?: number;
64
- _hasNulls?: boolean;
65
- _keysAreNumeric?: boolean;
66
- _accessPattern?: string;
67
- }
68
- /**
69
- * Nullable fields extracted from schema
70
- */
71
- export interface NullableFields {
72
- /** Fields that are always null */
73
- alwaysNull: string[];
74
- /** Fields that can be null (mixed types) */
75
- nullable: string[];
76
- }
77
56
  /**
78
57
  * Unified response format for all tool calls
79
58
  * Provides a consistent structure for LLM consumption
@@ -6,11 +6,3 @@
6
6
  * @returns Tool ID like "1697834567123_met_qry_a3b4c5"
7
7
  */
8
8
  export declare const generateToolId: (toolName: string, args: Record<string, unknown>, toolAbbreviations?: Record<string, string>) => string;
9
- /**
10
- * Generate LLM-friendly compact filename
11
- * @param toolName - Name of the tool that generated the data
12
- * @param args - Arguments passed to the tool
13
- * @param toolAbbreviations - Optional custom abbreviations for tool names
14
- * @returns Compact filename like "1697834567123_met_qry_a3b4c5.json"
15
- */
16
- export declare const generateCompactFilename: (toolName: string, args: Record<string, unknown>, toolAbbreviations?: Record<string, string>) => string;
@@ -40,13 +40,3 @@ export const generateToolId = (toolName, args, toolAbbreviations) => {
40
40
  const argsHash = hashArgs(args);
41
41
  return `${timestamp}_${toolAbbrev}_${argsHash}`;
42
42
  };
43
- /**
44
- * Generate LLM-friendly compact filename
45
- * @param toolName - Name of the tool that generated the data
46
- * @param args - Arguments passed to the tool
47
- * @param toolAbbreviations - Optional custom abbreviations for tool names
48
- * @returns Compact filename like "1697834567123_met_qry_a3b4c5.json"
49
- */
50
- export const generateCompactFilename = (toolName, args, toolAbbreviations) => {
51
- return `${generateToolId(toolName, args, toolAbbreviations)}.json`;
52
- };
@@ -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>;
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anyshift/mcp-proxy",
3
- "version": "0.3.5",
3
+ "version": "0.4.0",
4
4
  "description": "Generic MCP proxy that adds truncation, file writing, and JQ capabilities to any MCP server",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",