@lexq/cli 2.0.0 → 2.1.1
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/index.js +919 -762
- package/dist/mcp/register.d.ts +6 -6
- package/dist/mcp/register.js +86 -29
- package/package.json +5 -4
- package/skills/lexq-simulation/SKILL.md +5 -0
package/dist/mcp/register.d.ts
CHANGED
|
@@ -2,6 +2,12 @@ import { Implementation, McpServer } from '@modelcontextprotocol/server';
|
|
|
2
2
|
import { LosslessNumber } from 'lossless-json';
|
|
3
3
|
export { LosslessNumber } from 'lossless-json';
|
|
4
4
|
|
|
5
|
+
declare class ApiError extends Error {
|
|
6
|
+
readonly statusCode: number;
|
|
7
|
+
readonly errorCode: string | null;
|
|
8
|
+
constructor(statusCode: number, errorCode: string | null, message: string);
|
|
9
|
+
}
|
|
10
|
+
|
|
5
11
|
interface McpToolResult {
|
|
6
12
|
[key: string]: unknown;
|
|
7
13
|
content: Array<{
|
|
@@ -61,12 +67,6 @@ declare function formatUnregisteredFactWarning(meta: unknown): string | null;
|
|
|
61
67
|
*/
|
|
62
68
|
declare function formatValueDomainMismatchWarning(meta: unknown): string | null;
|
|
63
69
|
|
|
64
|
-
declare class ApiError extends Error {
|
|
65
|
-
readonly statusCode: number;
|
|
66
|
-
readonly errorCode: string | null;
|
|
67
|
-
constructor(statusCode: number, errorCode: string | null, message: string);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
70
|
/** Parses JSON text without rounding numbers that a double cannot hold. */
|
|
71
71
|
declare const parseJson: (text: string) => unknown;
|
|
72
72
|
/**
|
package/dist/mcp/register.js
CHANGED
|
@@ -31,9 +31,27 @@ function registerStatusTools(server, callApi) {
|
|
|
31
31
|
import { z as z2 } from "zod";
|
|
32
32
|
|
|
33
33
|
// src/types/enums.ts
|
|
34
|
+
var PolicyGroupStatus = ["ACTIVE", "DISABLED", "ARCHIVED"];
|
|
35
|
+
var LogicalOperator = ["AND", "OR"];
|
|
36
|
+
var MutateOperator = ["ASSIGN", "ADD", "SUB", "MUL", "DIV"];
|
|
37
|
+
var CalculationMethod = ["PERCENTAGE", "AMOUNT"];
|
|
38
|
+
var RoundingPolicy = [
|
|
39
|
+
"UP",
|
|
40
|
+
"DOWN",
|
|
41
|
+
"CEILING",
|
|
42
|
+
"FLOOR",
|
|
43
|
+
"HALF_UP",
|
|
44
|
+
"HALF_DOWN",
|
|
45
|
+
"HALF_EVEN"
|
|
46
|
+
];
|
|
34
47
|
var ValueType = ["STRING", "NUMBER", "BOOLEAN", "LIST_STRING", "LIST_NUMBER"];
|
|
35
48
|
var ConflictResolutionMode = ["NONE", "EXCLUSIVE", "MAX_N"];
|
|
36
49
|
var ConflictResolutionStrategy = ["HIGHEST_PRIORITY"];
|
|
50
|
+
var DeploymentType = ["DEPLOY", "ROLLBACK", "UNDEPLOY"];
|
|
51
|
+
var ApiExecutionStatus = ["SUCCESS", "NO_MATCH", "ERROR", "TIMEOUT"];
|
|
52
|
+
var ProfileCacheState = ["HIT", "MISS"];
|
|
53
|
+
var SimulationStatus = ["PENDING", "RUNNING", "COMPLETED", "FAILED", "CANCELED"];
|
|
54
|
+
var SimulationMetricType = ["COUNT", "SUM", "AVG"];
|
|
37
55
|
var ExportFormat = ["CSV", "JSON"];
|
|
38
56
|
var FailureStatus = ["PENDING", "RESOLVED", "IGNORED"];
|
|
39
57
|
var FailureAction = ["IGNORE", "RESOLVE"];
|
|
@@ -47,9 +65,12 @@ var PlatformEventType = [
|
|
|
47
65
|
"DEPLOY_SCHEDULE_CANCELED"
|
|
48
66
|
];
|
|
49
67
|
var WebhookPayloadFormat = ["GENERIC", "SLACK"];
|
|
68
|
+
var DeploymentScheduleStatus = ["PENDING", "EXECUTED", "CANCELED", "FAILED"];
|
|
50
69
|
|
|
51
70
|
// src/types/constants.ts
|
|
52
71
|
var MAX_ROUNDING_SCALE = 34;
|
|
72
|
+
var MIN_TRAFFIC_RATE = 1;
|
|
73
|
+
var MAX_TRAFFIC_RATE = 99;
|
|
53
74
|
var REPLAY_WINDOW_MAX_RECORDS = 5e4;
|
|
54
75
|
var DEFAULT_SIMULATION_RECORDS = 1e3;
|
|
55
76
|
var MAX_SIMULATION_RECORDS = 1e5;
|
|
@@ -66,6 +87,10 @@ var MAX_RULES_PER_VERSION = 100;
|
|
|
66
87
|
var MAX_CONDITIONS_PER_RULE = 25;
|
|
67
88
|
var MAX_CONDITION_DEPTH = 10;
|
|
68
89
|
var MAX_SNAPSHOT_BYTES = 131072;
|
|
90
|
+
var STATS_DEFAULT_WINDOW_DAYS = 30;
|
|
91
|
+
var STATS_MAX_WINDOW_DAYS = 31;
|
|
92
|
+
var FAILURE_LOG_LOOKBACK_DAYS = 7;
|
|
93
|
+
var PURGE_AFTER_DAYS = 30;
|
|
69
94
|
|
|
70
95
|
// src/mcp/tools/groups.ts
|
|
71
96
|
function registerGroupTools(server, callApi) {
|
|
@@ -132,7 +157,7 @@ function registerGroupTools(server, callApi) {
|
|
|
132
157
|
groupId: z2.string().uuid().describe("Policy group ID"),
|
|
133
158
|
name: z2.string().optional().describe("New name"),
|
|
134
159
|
description: z2.string().optional().describe("New description"),
|
|
135
|
-
status: z2.enum(["ACTIVE", "DISABLED"]).optional().describe(
|
|
160
|
+
status: z2.enum(PolicyGroupStatus).extract(["ACTIVE", "DISABLED"]).optional().describe(
|
|
136
161
|
"DISABLED stops production execution of this group: execution calls return 403 (P-009), and a composite call that includes the group is rejected in full. The deployed version and any A/B test are kept, and dry run, Impact Simulation, and Decision Replay keep working, so a fix can be verified before setting ACTIVE again. To go back to the previous version, use lexq_deploy_rollback. To take the version out of production, use lexq_deploy_undeploy."
|
|
137
162
|
),
|
|
138
163
|
activationGroup: z2.string().optional().describe(
|
|
@@ -152,7 +177,7 @@ function registerGroupTools(server, callApi) {
|
|
|
152
177
|
{
|
|
153
178
|
title: "Delete Policy Group",
|
|
154
179
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
155
|
-
description:
|
|
180
|
+
description: `Delete (archive) a policy group. A deployed group cannot be deleted (P-020); undeploy it first with lexq_deploy_undeploy, which also ends a running A/B test. This cannot be undone. The group no longer appears in lexq_groups_list and its name can be reused right away. Calls that change it (settings, versions, rules, starting an A/B test, publish, deploy, rollback, scheduling a deployment) return 409 P-059. If it was ever executed, deployed, or replayed, it is kept for audit; otherwise it is permanently deleted after ${PURGE_AFTER_DAYS} days.`,
|
|
156
181
|
inputSchema: z2.object({
|
|
157
182
|
groupId: z2.string().uuid().describe("Policy group ID")
|
|
158
183
|
})
|
|
@@ -186,7 +211,9 @@ function registerGroupTools(server, callApi) {
|
|
|
186
211
|
inputSchema: z2.object({
|
|
187
212
|
groupId: z2.string().uuid().describe("Policy group ID"),
|
|
188
213
|
testVersionId: z2.string().uuid().describe("Challenger version ID. Must be a published version of this group"),
|
|
189
|
-
trafficRate: z2.number().int().min(
|
|
214
|
+
trafficRate: z2.number().int().min(MIN_TRAFFIC_RATE).max(MAX_TRAFFIC_RATE).describe(
|
|
215
|
+
`Traffic percentage routed to challenger (${MIN_TRAFFIC_RATE}-${MAX_TRAFFIC_RATE})`
|
|
216
|
+
)
|
|
190
217
|
})
|
|
191
218
|
},
|
|
192
219
|
async ({ groupId, ...body }) => callApi("POST", `policy-groups/${groupId}/ab-test`, { body })
|
|
@@ -211,7 +238,9 @@ function registerGroupTools(server, callApi) {
|
|
|
211
238
|
description: "Adjust traffic rate of a running A/B test.",
|
|
212
239
|
inputSchema: z2.object({
|
|
213
240
|
groupId: z2.string().uuid().describe("Policy group ID"),
|
|
214
|
-
trafficRate: z2.number().int().min(
|
|
241
|
+
trafficRate: z2.number().int().min(MIN_TRAFFIC_RATE).max(MAX_TRAFFIC_RATE).describe(
|
|
242
|
+
`New traffic percentage for challenger (${MIN_TRAFFIC_RATE}-${MAX_TRAFFIC_RATE})`
|
|
243
|
+
)
|
|
215
244
|
})
|
|
216
245
|
},
|
|
217
246
|
async ({ groupId, ...body }) => callApi("PATCH", `policy-groups/${groupId}/ab-test/traffic-rate`, { body })
|
|
@@ -324,9 +353,6 @@ var END_DAY = `End date, ${ORG_DAY}. Inclusive: the whole day counts.`;
|
|
|
324
353
|
var WINDOW_START_DAY = `Window start date, ${ORG_DAY}. Inclusive.`;
|
|
325
354
|
var WINDOW_END_DAY = `Window end date, ${ORG_DAY}. Inclusive: the whole day counts.`;
|
|
326
355
|
var INSTANT = "ISO-8601 instant with Z or an offset, e.g. 2026-10-01T00:00:00Z or 2026-10-01T09:00:00+09:00. A bare date or a time without an offset is rejected.";
|
|
327
|
-
var STATS_DEFAULT_WINDOW_DAYS = 30;
|
|
328
|
-
var STATS_MAX_WINDOW_DAYS = 31;
|
|
329
|
-
var FAILURE_LOG_LOOKBACK_DAYS = 7;
|
|
330
356
|
|
|
331
357
|
// src/mcp/tools/versions.ts
|
|
332
358
|
function registerVersionTools(server, callApi) {
|
|
@@ -427,6 +453,7 @@ function registerVersionTools(server, callApi) {
|
|
|
427
453
|
// src/mcp/tools/rules.ts
|
|
428
454
|
import { z as z4 } from "zod";
|
|
429
455
|
import dedent from "dedent";
|
|
456
|
+
var oneOf = (values) => values.map((v) => `"${v}"`).join("|");
|
|
430
457
|
function registerRuleTools(server, callApi) {
|
|
431
458
|
server.registerTool(
|
|
432
459
|
"lexq_rules_list",
|
|
@@ -476,8 +503,8 @@ function registerRuleTools(server, callApi) {
|
|
|
476
503
|
After saving, lexq_facts_unregistered lists any keys this version references but has not
|
|
477
504
|
defined (non-blocking, version-wide) — use it to decide what to register.
|
|
478
505
|
|
|
479
|
-
Condition: { type: "SINGLE", field, operator, value, valueType } or { type: "GROUP", operator:
|
|
480
|
-
Value types:
|
|
506
|
+
Condition: { type: "SINGLE", field, operator, value, valueType } or { type: "GROUP", operator: ${oneOf(LogicalOperator)}, children: [...] }
|
|
507
|
+
Value types: ${ValueType.join(", ")}
|
|
481
508
|
Limits: a condition tree may hold at most ${MAX_CONDITIONS_PER_RULE} conditions (SINGLE nodes; GROUP nodes do not count)
|
|
482
509
|
and nest at most ${MAX_CONDITION_DEPTH} levels below the root. A version holds at most ${MAX_RULES_PER_VERSION} rules.
|
|
483
510
|
|
|
@@ -500,7 +527,7 @@ function registerRuleTools(server, callApi) {
|
|
|
500
527
|
Actions: [{ type, parameters }]
|
|
501
528
|
|
|
502
529
|
Action parameter schemas:
|
|
503
|
-
- MUTATE_FACT: { targetVar: string, operator:
|
|
530
|
+
- MUTATE_FACT: { targetVar: string, operator: ${oneOf(MutateOperator)}, method: ${oneOf(CalculationMethod)}, operand: number, refVar?: string, rounding?: RoundingOption }
|
|
504
531
|
targetVar is the fact this action reads and writes. It must be a registered fact
|
|
505
532
|
(ACT-032 otherwise), and the request must send it (P-015 otherwise). Its value must be
|
|
506
533
|
a number when the action runs.
|
|
@@ -539,7 +566,7 @@ function registerRuleTools(server, callApi) {
|
|
|
539
566
|
writes, send that value as an input fact in a second request. If a rule should run
|
|
540
567
|
whenever another one does, put its actions in that rule instead.
|
|
541
568
|
|
|
542
|
-
RoundingOption (optional, MUTATE_FACT only): { scale: integer (0..${MAX_ROUNDING_SCALE}), mode?:
|
|
569
|
+
RoundingOption (optional, MUTATE_FACT only): { scale: integer (0..${MAX_ROUNDING_SCALE}), mode?: ${oneOf(RoundingPolicy)} } mode defaults to HALF_UP. When omitted, calculator output is preserved at full precision (lossless).
|
|
543
570
|
`,
|
|
544
571
|
inputSchema: z4.object({
|
|
545
572
|
groupId: z4.string().uuid().describe("Policy group ID"),
|
|
@@ -674,7 +701,7 @@ function registerFactTools(server, callApi) {
|
|
|
674
701
|
{
|
|
675
702
|
title: "Create Fact Definition",
|
|
676
703
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
677
|
-
description:
|
|
704
|
+
description: `Register a new input variable. Key starts with a letter, then letters, numbers, and underscores (e.g. paymentAmount). Casing is not enforced. Types: ${VALUE_TYPES.join(", ")}.`,
|
|
678
705
|
inputSchema: z5.object({
|
|
679
706
|
key: z5.string().regex(FACT_KEY_PATTERN).describe("Variable key. Any casing; must start with a letter."),
|
|
680
707
|
name: z5.string().describe("Display name"),
|
|
@@ -880,7 +907,7 @@ function registerDeployTools(server, callApi) {
|
|
|
880
907
|
{
|
|
881
908
|
title: "List Scheduled Deployments",
|
|
882
909
|
annotations: { readOnlyHint: true },
|
|
883
|
-
description:
|
|
910
|
+
description: `List scheduled deployments across all groups (all statuses: ${DeploymentScheduleStatus.join(", ")}), newest first.`,
|
|
884
911
|
inputSchema: z6.object({
|
|
885
912
|
page: z6.number().int().min(0).default(0).describe("Page number"),
|
|
886
913
|
size: z6.number().int().min(1).max(100).default(20).describe("Page size")
|
|
@@ -898,7 +925,7 @@ function registerDeployTools(server, callApi) {
|
|
|
898
925
|
page: z6.number().int().min(0).default(0).describe("Page number"),
|
|
899
926
|
size: z6.number().int().min(1).max(100).default(20).describe("Page size"),
|
|
900
927
|
groupId: z6.string().uuid().optional().describe("Filter by group ID"),
|
|
901
|
-
types: z6.string().optional().describe(
|
|
928
|
+
types: z6.string().optional().describe(`Filter by deployment types (comma-separated: ${DeploymentType.join(",")})`),
|
|
902
929
|
startDate: z6.string().optional().describe(START_DAY),
|
|
903
930
|
endDate: z6.string().optional().describe(END_DAY)
|
|
904
931
|
})
|
|
@@ -966,6 +993,13 @@ function registerDeployTools(server, callApi) {
|
|
|
966
993
|
// src/mcp/tools/analytics.ts
|
|
967
994
|
import { z as z7 } from "zod";
|
|
968
995
|
import dedent3 from "dedent";
|
|
996
|
+
function datasetBlock(path) {
|
|
997
|
+
const dataset = { type: "UPLOADED", source: "S3_BUCKET", path };
|
|
998
|
+
return {
|
|
999
|
+
type: "text",
|
|
1000
|
+
text: "Ready-to-use dataset block for lexq_simulation_start:\n" + stringifyJson({ dataset }, 2)
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
969
1003
|
function registerAnalyticsTools(server, callApi) {
|
|
970
1004
|
server.registerTool(
|
|
971
1005
|
"lexq_dry_run",
|
|
@@ -1054,7 +1088,7 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
1054
1088
|
options.baselinePolicyVersionId: uuid (optional, for baseline comparison)
|
|
1055
1089
|
options.includeRuleStats: boolean
|
|
1056
1090
|
options.metricConfig: optional — omit for plain execution count. To aggregate a fact, pass
|
|
1057
|
-
{ "targetVariable": "<fact>", "aggregationType":
|
|
1091
|
+
{ "targetVariable": "<fact>", "aggregationType": ${SimulationMetricType.map((t) => `"${t}"`).join(" | ")} }
|
|
1058
1092
|
|
|
1059
1093
|
Example (uploaded dataset):
|
|
1060
1094
|
{
|
|
@@ -1100,7 +1134,7 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
1100
1134
|
inputSchema: z7.object({
|
|
1101
1135
|
page: z7.number().int().min(0).default(0).describe("Page number"),
|
|
1102
1136
|
size: z7.number().int().min(1).max(100).default(20).describe("Page size"),
|
|
1103
|
-
status: z7.enum(
|
|
1137
|
+
status: z7.enum(SimulationStatus).optional().describe("Filter by status"),
|
|
1104
1138
|
from: z7.string().optional().describe(START_DAY),
|
|
1105
1139
|
to: z7.string().optional().describe(END_DAY)
|
|
1106
1140
|
})
|
|
@@ -1152,6 +1186,10 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
1152
1186
|
To use the returned path in lexq_simulation_start, set:
|
|
1153
1187
|
dataset: { "type": "UPLOADED", "source": "S3_BUCKET", "path": "<returned path>" }
|
|
1154
1188
|
|
|
1189
|
+
The content travels inside the tool call, so this suits small datasets, and the hosted server at
|
|
1190
|
+
mcp.lexq.io limits the size of a request. For a larger file, use lexq_dataset_upload_file on the
|
|
1191
|
+
local server (lexq serve --mcp), or upload it with the CLI or the console and pass the returned path.
|
|
1192
|
+
|
|
1155
1193
|
CSV example:
|
|
1156
1194
|
userId,paymentAmount
|
|
1157
1195
|
user_001,150000
|
|
@@ -1171,13 +1209,8 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
1171
1209
|
if (!result.isError) {
|
|
1172
1210
|
try {
|
|
1173
1211
|
const uploaded = parseJson(result.content[0]?.text ?? "{}");
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
result.content.push({
|
|
1177
|
-
type: "text",
|
|
1178
|
-
text: "Ready-to-use dataset block for lexq_simulation_start:\n" + stringifyJson({ dataset }, 2)
|
|
1179
|
-
});
|
|
1180
|
-
}
|
|
1212
|
+
const path = uploaded.data?.path ?? uploaded.path;
|
|
1213
|
+
if (typeof path === "string" && path) result.content.push(datasetBlock(path));
|
|
1181
1214
|
} catch {
|
|
1182
1215
|
}
|
|
1183
1216
|
}
|
|
@@ -1204,6 +1237,15 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
1204
1237
|
|
|
1205
1238
|
// src/mcp/tools/profile.ts
|
|
1206
1239
|
import { z as z8 } from "zod";
|
|
1240
|
+
|
|
1241
|
+
// src/lib/percentiles.ts
|
|
1242
|
+
var PERCENTILE_MIN_N = {
|
|
1243
|
+
p50: TAIL_MIN_OBS * 2,
|
|
1244
|
+
p95: TAIL_MIN_OBS * 20,
|
|
1245
|
+
p99: TAIL_MIN_OBS * 100
|
|
1246
|
+
};
|
|
1247
|
+
|
|
1248
|
+
// src/mcp/tools/profile.ts
|
|
1207
1249
|
var RELATIVE_THRESHOLD = `flagged = p50 \u2265 ${SLOW_MULTIPLIER}\xD7 median of per-rule p50s within the group; absolute thresholds are intentionally not supported.`;
|
|
1208
1250
|
function profileParams(opts) {
|
|
1209
1251
|
const params = {};
|
|
@@ -1219,7 +1261,7 @@ function registerProfileTools(server, callApi) {
|
|
|
1219
1261
|
{
|
|
1220
1262
|
title: "Group Latency Profile",
|
|
1221
1263
|
annotations: { readOnlyHint: true },
|
|
1222
|
-
description: "Per-rule latency profile of a policy group over a time window: group TOTAL distribution split by cache state (HIT = compiled ruleset cache hit, MISS = deep-load + compile), a per-rule CONDITION/ACTION percentile table, and slow-rule flags. " + RELATIVE_THRESHOLD + ` Every percentile is accompanied by its sample count n; a percentile is withheld (null) unless n\xD7(1\u2212q) \u2265 ${TAIL_MIN_OBS} (p50 needs n \u2265
|
|
1264
|
+
description: "Per-rule latency profile of a policy group over a time window: group TOTAL distribution split by cache state (HIT = compiled ruleset cache hit, MISS = deep-load + compile), a per-rule CONDITION/ACTION percentile table, and slow-rule flags. " + RELATIVE_THRESHOLD + ` Every percentile is accompanied by its sample count n; a percentile is withheld (null) unless n\xD7(1\u2212q) \u2265 ${TAIL_MIN_OBS} (p50 needs n \u2265 ${PERCENTILE_MIN_N.p50}, p95 n \u2265 ${PERCENTILE_MIN_N.p95}, p99 n \u2265 ${PERCENTILE_MIN_N.p99} \u2014 display gate, separate from the n \u2265 ${MIN_SAMPLES} judgment gate). Baselines report INSUFFICIENT_COHORT when fewer than ${MIN_COHORT_SIZE} rules qualify. Rule detail comes from a deterministic ${PROFILE_SAMPLE_PERMILLE / 10}% sample of calls; TOTAL is recorded for every call. Defaults: last ${PROFILE_DEFAULT_WINDOW_HOURS}h, live version, cacheState HIT.`,
|
|
1223
1265
|
inputSchema: z8.object({
|
|
1224
1266
|
groupId: z8.string().uuid().describe("Policy group ID"),
|
|
1225
1267
|
versionId: z8.string().uuid().optional().describe("Version to inspect (default: live version)"),
|
|
@@ -1227,7 +1269,7 @@ function registerProfileTools(server, callApi) {
|
|
|
1227
1269
|
`Window start, ISO-8601 instant (e.g. 2026-07-01T00:00:00Z). Default: ${PROFILE_DEFAULT_WINDOW_HOURS}h ago`
|
|
1228
1270
|
),
|
|
1229
1271
|
to: z8.string().optional().describe("Window end, ISO-8601 instant. Default: now"),
|
|
1230
|
-
cacheState: z8.enum(
|
|
1272
|
+
cacheState: z8.enum(ProfileCacheState).optional().describe("Cache dimension for the rule table and judgment (default: HIT)")
|
|
1231
1273
|
})
|
|
1232
1274
|
},
|
|
1233
1275
|
async ({ groupId, versionId, from, to, cacheState }) => callApi("GET", `policy-groups/${groupId}/profile`, {
|
|
@@ -1239,7 +1281,7 @@ function registerProfileTools(server, callApi) {
|
|
|
1239
1281
|
{
|
|
1240
1282
|
title: "Rule Latency Detail",
|
|
1241
1283
|
annotations: { readOnlyHint: true },
|
|
1242
|
-
description: `Single-rule latency detail: merged phase \xD7 cacheState distributions plus a per-window time series (${LATENCY_WINDOW_MILLIS / 1e3}s windows). Missing windows are genuine gaps \u2014 never interpolated. Series points carry each window's own values; percentiles in merged distributions are withheld (null) unless n\xD7(1\u2212q) \u2265 ${TAIL_MIN_OBS} (p50 n \u2265
|
|
1284
|
+
description: `Single-rule latency detail: merged phase \xD7 cacheState distributions plus a per-window time series (${LATENCY_WINDOW_MILLIS / 1e3}s windows). Missing windows are genuine gaps \u2014 never interpolated. Series points carry each window's own values; percentiles in merged distributions are withheld (null) unless n\xD7(1\u2212q) \u2265 ${TAIL_MIN_OBS} (p50 n \u2265 ${PERCENTILE_MIN_N.p50}, p95 n \u2265 ${PERCENTILE_MIN_N.p95}, p99 n \u2265 ${PERCENTILE_MIN_N.p99}). ` + RELATIVE_THRESHOLD,
|
|
1243
1285
|
inputSchema: z8.object({
|
|
1244
1286
|
groupId: z8.string().uuid().describe("Policy group ID"),
|
|
1245
1287
|
ruleId: z8.string().uuid().describe("Rule ID (from lexq_profile_overview)"),
|
|
@@ -1356,7 +1398,7 @@ function registerHistoryTools(server, callApi) {
|
|
|
1356
1398
|
traceId: z10.string().optional().describe("Filter by trace ID"),
|
|
1357
1399
|
groupId: z10.string().uuid().optional().describe("Filter by policy group"),
|
|
1358
1400
|
versionId: z10.string().uuid().optional().describe("Filter by version"),
|
|
1359
|
-
status: z10.enum(
|
|
1401
|
+
status: z10.enum(ApiExecutionStatus).optional().describe("Filter by execution status"),
|
|
1360
1402
|
startDate: z10.string().optional().describe(START_DAY),
|
|
1361
1403
|
endDate: z10.string().optional().describe(END_DAY)
|
|
1362
1404
|
})
|
|
@@ -1451,6 +1493,17 @@ function registerProvenanceTools(server, callApi) {
|
|
|
1451
1493
|
|
|
1452
1494
|
// src/mcp/tools/logs.ts
|
|
1453
1495
|
import { z as z12 } from "zod";
|
|
1496
|
+
|
|
1497
|
+
// src/lib/enum-notes.ts
|
|
1498
|
+
function withNotes(notes) {
|
|
1499
|
+
return Object.entries(notes).map(([value, note]) => note ? `${value} (${note})` : value);
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
// src/mcp/tools/logs.ts
|
|
1503
|
+
var ACTION_NOTES = {
|
|
1504
|
+
RESOLVE: "mark as manually fixed",
|
|
1505
|
+
IGNORE: "skip intentionally"
|
|
1506
|
+
};
|
|
1454
1507
|
function registerLogTools(server, callApi) {
|
|
1455
1508
|
server.registerTool(
|
|
1456
1509
|
"lexq_logs_list",
|
|
@@ -1495,7 +1548,7 @@ function registerLogTools(server, callApi) {
|
|
|
1495
1548
|
{
|
|
1496
1549
|
title: "Process Failure Log",
|
|
1497
1550
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
1498
|
-
description:
|
|
1551
|
+
description: `Process a single failure log: ${withNotes(ACTION_NOTES).join(" or ")}.`,
|
|
1499
1552
|
inputSchema: z12.object({
|
|
1500
1553
|
logId: z12.string().uuid().describe("Failure log ID"),
|
|
1501
1554
|
action: z12.enum(FailureAction).describe("Action to take")
|
|
@@ -1524,6 +1577,10 @@ function registerLogTools(server, callApi) {
|
|
|
1524
1577
|
|
|
1525
1578
|
// src/mcp/tools/webhook-subscriptions.ts
|
|
1526
1579
|
import { z as z13 } from "zod";
|
|
1580
|
+
var FORMAT_NOTES = {
|
|
1581
|
+
GENERIC: "full JSON",
|
|
1582
|
+
SLACK: '{"text": "..."}'
|
|
1583
|
+
};
|
|
1527
1584
|
function registerWebhookSubscriptionTools(server, callApi) {
|
|
1528
1585
|
server.registerTool(
|
|
1529
1586
|
"lexq_webhook_subscriptions_list",
|
|
@@ -1558,7 +1615,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
|
|
|
1558
1615
|
{
|
|
1559
1616
|
title: "Save Webhook Subscription",
|
|
1560
1617
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
1561
|
-
description: `Create or update a webhook subscription. Omit id to create, provide id to update. Events: ${PlatformEventType.join(", ")}. Formats:
|
|
1618
|
+
description: `Create or update a webhook subscription. Omit id to create, provide id to update. Events: ${PlatformEventType.join(", ")}. Formats: ${withNotes(FORMAT_NOTES).join(", ")}.`,
|
|
1562
1619
|
inputSchema: z13.object({
|
|
1563
1620
|
id: z13.string().uuid().optional().describe("Subscription ID (omit to create, provide to update)"),
|
|
1564
1621
|
name: z13.string().min(1).describe("Subscription name (unique per tenant)"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lexq/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"mcpName": "io.lexq/lexq",
|
|
5
5
|
"description": "The Decision Operations Platform for engineering teams",
|
|
6
6
|
"type": "module",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"test:value-domain": "pnpm build && node tests/value-domain.mjs",
|
|
46
46
|
"test:fact-update": "pnpm build && node tests/fact-update.mjs",
|
|
47
47
|
"test:rollback": "pnpm build && node tests/rollback.mjs",
|
|
48
|
+
"test:dataset-upload": "pnpm build && node tests/dataset-upload.mjs",
|
|
48
49
|
"prepublishOnly": "pnpm build",
|
|
49
50
|
"knip": "knip",
|
|
50
51
|
"format": "prettier --write \"src/**/*.ts\"",
|
|
@@ -76,16 +77,16 @@
|
|
|
76
77
|
},
|
|
77
78
|
"packageManager": "pnpm@10.28.0",
|
|
78
79
|
"dependencies": {
|
|
79
|
-
"@modelcontextprotocol/server": "^2.
|
|
80
|
+
"@modelcontextprotocol/server": "^2.3.1",
|
|
80
81
|
"cli-table3": "^0.6.5",
|
|
81
82
|
"commander": "^13.1.0",
|
|
82
83
|
"dedent": "^1.7.2",
|
|
83
84
|
"lossless-json": "4.3.1",
|
|
84
|
-
"zod": "^4.
|
|
85
|
+
"zod": "^4.6.7"
|
|
85
86
|
},
|
|
86
87
|
"devDependencies": {
|
|
87
88
|
"@eslint/js": "^9.20.0",
|
|
88
|
-
"@modelcontextprotocol/client": "^2.
|
|
89
|
+
"@modelcontextprotocol/client": "^2.3.1",
|
|
89
90
|
"@types/node": "^22.12.0",
|
|
90
91
|
"eslint": "^10.2.0",
|
|
91
92
|
"knip": "^6.29.0",
|
|
@@ -280,6 +280,11 @@ lexq analytics simulation start --json '{
|
|
|
280
280
|
**CSV format:** Header row with fact keys, data rows with values. Types auto-detected.
|
|
281
281
|
**JSON format:** Array of objects `[{"key": "value"}, ...]`
|
|
282
282
|
|
|
283
|
+
Dataset files can be up to 10 MB. Over MCP, the local server (`lexq serve --mcp`) has
|
|
284
|
+
`lexq_dataset_upload_file`, which takes the absolute path of a `.csv` or `.json` file and reads it from
|
|
285
|
+
disk. The hosted server has only `lexq_dataset_upload`, which takes the content inline and suits small
|
|
286
|
+
datasets. For a larger file there, upload it with the command above and pass the returned `path`.
|
|
287
|
+
|
|
283
288
|
### Check Status (Poll)
|
|
284
289
|
|
|
285
290
|
```bash
|