detect_anomaly
Flag outlier points in numeric series using Z-score or IQR. Returns indices and values to identify anomalies in metrics, fraud signals, or sensor noise.
Instructions
[Premium] Flag outlier points in a numeric series using Z-score (parametric, assumes ~normal) or IQR (robust to skew). Use for monitoring metrics, fraud signals, sensor noise, quality control. Z-score is faster and tighter on near-normal data; IQR is the right default when the distribution has heavy tails or known outliers. Returns indices + values + the underlying statistics. For projecting a series forward, use predict_forecast. Requires ORACLAW_API_KEY.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| data | Yes | Numeric series to scan. | |
| method | No | Default: zscore. | |
| threshold | No | Z-score: standard deviations above mean (default: 3.0). IQR: multiplier on IQR (default: 1.5). |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| method | Yes | ||
| anomalies | Yes | ||
| stats | No | For zscore: {mean, stdDev, threshold}. For iqr: {q1, q3, iqr, lowerBound, upperBound}. | |
| totalPoints | No | ||
| anomalyCount | Yes |
Implementation Reference
- Core Z-Score anomaly detection handler. Computes mean & stdDev from data using simple-statistics, then flags points whose absolute z-score exceeds threshold.
export function detectAnomaliesZScore( data: number[], threshold: number = 3.0, ): AnomalyResult { if (data.length < 2) { return { anomalies: [], mean: data[0] ?? 0, stdDev: 0, threshold }; } const mu = ssMean(data); const sigma = ssStdDev(data); if (sigma === 0) { return { anomalies: [], mean: mu, stdDev: 0, threshold }; } const anomalies: Array<{ index: number; value: number; zScore: number }> = []; for (let i = 0; i < data.length; i++) { const z = (data[i]! - mu) / sigma; if (Math.abs(z) > threshold) { anomalies.push({ index: i, value: data[i]!, zScore: z }); } } return { anomalies, mean: mu, stdDev: sigma, threshold }; } - Core IQR anomaly detection handler. Computes Q1, Q3, and IQR using simple-statistics, then flags points outside [Q1-k*IQR, Q3+k*IQR].
export function detectAnomaliesIQR( data: number[], k: number = 1.5, ): IQRAnomalyResult { if (data.length < 4) { const q1 = data.length > 0 ? ssQuantile(data, 0.25) : 0; const q3 = data.length > 0 ? ssQuantile(data, 0.75) : 0; const iqr = q3 - q1; return { anomalies: [], q1, q3, iqr, lowerBound: q1 - k * iqr, upperBound: q3 + k * iqr, }; } const q1 = ssQuantile(data, 0.25); const q3 = ssQuantile(data, 0.75); const iqr = ssIQR(data); const lowerBound = q1 - k * iqr; const upperBound = q3 + k * iqr; const anomalies: Array<{ index: number; value: number }> = []; for (let i = 0; i < data.length; i++) { if (data[i]! < lowerBound || data[i]! > upperBound) { anomalies.push({ index: i, value: data[i]! }); } } return { anomalies, q1, q3, iqr, lowerBound, upperBound }; } - MCP tool schema registration for 'detect_anomaly'. Defines input: array of numbers, method enum (zscore/iqr), threshold number.
{ name: "detect_anomaly", description: "Anomaly detection in numeric data using Z-Score or IQR methods. Z-Score flags points exceeding a standard deviation threshold (parametric, assumes normality). IQR flags points outside interquartile fences (non-parametric, robust to skew). Use for fraud detection, quality control, sensor monitoring, or data cleaning.", inputSchema: { type: "object" as const, properties: { data: { type: "array", items: { type: "number" }, description: "Numeric observations to scan for anomalies", }, method: { type: "string", enum: ["zscore", "iqr"], description: "Detection method (default: zscore)", }, threshold: { type: "number", description: "Z-score cutoff (default 3.0) or IQR multiplier (default 1.5)", }, }, required: ["data"], }, - mission-control/packages/mcp-server/src/index.ts:631-644 (registration)MCP server case handler routing 'detect_anomaly' calls to detectAnomaliesIQR or detectAnomaliesZScore functions.
case "detect_anomaly": { const { data, method, threshold } = args as { data: number[]; method?: "zscore" | "iqr"; threshold?: number; }; const m = method ?? "zscore"; if (m === "iqr") { const result = detectAnomaliesIQR(data, threshold ?? 1.5); return { content: [{ type: "text", text: JSON.stringify({ method: "iqr", anomalies: result.anomalies, q1: result.q1, q3: result.q3, iqr: result.iqr, lowerBound: result.lowerBound, upperBound: result.upperBound, count: result.anomalies.length }, null, 2) }] }; } const result = detectAnomaliesZScore(data, threshold ?? 3.0); return { content: [{ type: "text", text: JSON.stringify({ method: "zscore", anomalies: result.anomalies, mean: result.mean, stdDev: result.stdDev, threshold: result.threshold, count: result.anomalies.length }, null, 2) }] }; } - Full anomaly detector module with types (AnomalyResult, IQRAnomalyResult, StreamingResult) and the StreamingAnomalyDetector class (Welford's online algorithm).
/** * Anomaly Detection * * Three complementary methods for identifying outliers in numeric data: * 1. Z-Score — parametric, assumes approximate normality * 2. IQR (Interquartile Range) — non-parametric, robust to skew * 3. StreamingAnomalyDetector — Welford's online algorithm for real-time feeds * * Uses `simple-statistics` for robust z-score and IQR primitives. */ import { mean as ssMean, standardDeviation as ssStdDev, quantile as ssQuantile, interquartileRange as ssIQR, } from "simple-statistics"; // ── Types ──────────────────────────────────────────────── /** Result of a batch z-score anomaly scan */ export interface AnomalyResult { /** Detected anomalies with index, value, and z-score */ anomalies: Array<{ index: number; value: number; zScore: number }>; /** Population mean */ mean: number; /** Population standard deviation */ stdDev: number; /** Z-score threshold that was applied */ threshold: number; } /** Result of a batch IQR anomaly scan */ export interface IQRAnomalyResult { /** Detected anomalies with index and value */ anomalies: Array<{ index: number; value: number }>; /** First quartile (25th percentile) */ q1: number; /** Third quartile (75th percentile) */ q3: number; /** Interquartile range (q3 - q1) */ iqr: number; /** Lower fence: q1 - k * iqr */ lowerBound: number; /** Upper fence: q3 + k * iqr */ upperBound: number; } /** Single observation result from the streaming detector */ export interface StreamingResult { /** Whether this value was flagged as an anomaly */ isAnomaly: boolean; /** Absolute z-score of this observation */ zScore: number; /** The observed value */ value: number; /** Running mean after incorporating this value */ mean: number; /** Running standard deviation after incorporating this value */ stdDev: number; } // ── Z-Score Detector ───────────────────────────────────── /** * Detect anomalies using the z-score method. * * A data point is an anomaly if its absolute z-score exceeds `threshold`. * Default threshold of 3.0 corresponds to ~0.27 % of a normal distribution. * * @param data - Numeric observations * @param threshold - z-score cutoff (default 3.0) * @returns AnomalyResult with flagged indices, mean, stdDev, and threshold * * O(n) time, O(k) space where k = number of anomalies */ export function detectAnomaliesZScore( data: number[], threshold: number = 3.0, ): AnomalyResult { if (data.length < 2) { return { anomalies: [], mean: data[0] ?? 0, stdDev: 0, threshold }; } const mu = ssMean(data); const sigma = ssStdDev(data); if (sigma === 0) { return { anomalies: [], mean: mu, stdDev: 0, threshold }; } const anomalies: Array<{ index: number; value: number; zScore: number }> = []; for (let i = 0; i < data.length; i++) { const z = (data[i]! - mu) / sigma; if (Math.abs(z) > threshold) { anomalies.push({ index: i, value: data[i]!, zScore: z }); } } return { anomalies, mean: mu, stdDev: sigma, threshold }; } // ── IQR Detector ───────────────────────────────────────── /** * Detect anomalies using the Interquartile Range (IQR) method. * * Values outside [Q1 - k*IQR, Q3 + k*IQR] are flagged. The default * multiplier k = 1.5 flags "mild" outliers; k = 3.0 flags "extreme" outliers. * * @param data - Numeric observations * @param k - IQR multiplier (default 1.5) * @returns IQRAnomalyResult with flagged indices and fence values * * O(n log n) due to quantile computation, O(k) space */ export function detectAnomaliesIQR( data: number[], k: number = 1.5, ): IQRAnomalyResult { if (data.length < 4) { const q1 = data.length > 0 ? ssQuantile(data, 0.25) : 0; const q3 = data.length > 0 ? ssQuantile(data, 0.75) : 0; const iqr = q3 - q1; return { anomalies: [], q1, q3, iqr, lowerBound: q1 - k * iqr, upperBound: q3 + k * iqr, }; } const q1 = ssQuantile(data, 0.25); const q3 = ssQuantile(data, 0.75); const iqr = ssIQR(data); const lowerBound = q1 - k * iqr; const upperBound = q3 + k * iqr; const anomalies: Array<{ index: number; value: number }> = []; for (let i = 0; i < data.length; i++) { if (data[i]! < lowerBound || data[i]! > upperBound) { anomalies.push({ index: i, value: data[i]! }); } } return { anomalies, q1, q3, iqr, lowerBound, upperBound }; }