@lexq/cli 1.1.7 → 2.1.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/index.js +859 -736
- package/dist/mcp/register.d.ts +6 -6
- package/dist/mcp/register.js +30 -17
- package/package.json +12 -6
- package/skills/lexq-recipes/SKILL.md +11 -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
|
@@ -84,7 +84,7 @@ function registerGroupTools(server, callApi) {
|
|
|
84
84
|
{
|
|
85
85
|
title: "Get Policy Group",
|
|
86
86
|
annotations: { readOnlyHint: true },
|
|
87
|
-
description: "Get a single policy group by ID.",
|
|
87
|
+
description: "Get a single policy group by ID. Its currentVersionId and currentDeploymentId are what lexq_deploy_rollback expects, and previousVersionName is the version a rollback restores.",
|
|
88
88
|
inputSchema: z2.object({
|
|
89
89
|
groupId: z2.string().uuid().describe("Policy group ID")
|
|
90
90
|
})
|
|
@@ -778,6 +778,7 @@ function registerFactTools(server, callApi) {
|
|
|
778
778
|
}
|
|
779
779
|
|
|
780
780
|
// src/mcp/tools/deploy.ts
|
|
781
|
+
import dedent2 from "dedent";
|
|
781
782
|
import { z as z6 } from "zod";
|
|
782
783
|
function registerDeployTools(server, callApi) {
|
|
783
784
|
server.registerTool(
|
|
@@ -815,14 +816,20 @@ function registerDeployTools(server, callApi) {
|
|
|
815
816
|
{
|
|
816
817
|
title: "Rollback Deployment",
|
|
817
818
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
818
|
-
description:
|
|
819
|
+
description: dedent2`
|
|
820
|
+
Roll the group back to the version that was live before the current one. Call lexq_groups_get first: currentVersionName is live now, previousVersionName is what a rollback restores, and a rollback is rejected while canRollback is false. A rollback needs a previous version that is still ACTIVE and whose effective end has not passed (P-058).
|
|
821
|
+
|
|
822
|
+
Pass the currentVersionId and currentDeploymentId from that call as expectedCurrentVersionId and expectedCurrentDeploymentId. If anyone deployed, rolled back or undeployed in between, the rollback is rejected with 409 and nothing changes: P-062 if another version or no version is live, P-063 if the same version is live but from a newer deployment. Read the group again and check with the user before retrying.
|
|
823
|
+
`,
|
|
819
824
|
inputSchema: z6.object({
|
|
820
825
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
826
|
+
expectedCurrentVersionId: z6.string().uuid().describe("currentVersionId from your last read of the group"),
|
|
827
|
+
expectedCurrentDeploymentId: z6.string().uuid().describe("currentDeploymentId from your last read of the group"),
|
|
821
828
|
memo: z6.string().min(1).describe("Rollback reason (required)")
|
|
822
829
|
})
|
|
823
830
|
},
|
|
824
|
-
async ({ groupId, memo }) => callApi("POST", `policy-groups/${groupId}/rollback`, {
|
|
825
|
-
body: { memo }
|
|
831
|
+
async ({ groupId, expectedCurrentVersionId, expectedCurrentDeploymentId, memo }) => callApi("POST", `policy-groups/${groupId}/rollback`, {
|
|
832
|
+
body: { memo, expectedCurrentVersionId, expectedCurrentDeploymentId }
|
|
826
833
|
})
|
|
827
834
|
);
|
|
828
835
|
server.registerTool(
|
|
@@ -922,7 +929,7 @@ function registerDeployTools(server, callApi) {
|
|
|
922
929
|
{
|
|
923
930
|
title: "Deployment Overview",
|
|
924
931
|
annotations: { readOnlyHint: true },
|
|
925
|
-
description: "Show current deployment status of all groups \u2014 which version is live, last deployment type, and deployer.",
|
|
932
|
+
description: "Show current deployment status of all groups \u2014 which version is live, last deployment type, and deployer. Each group also carries currentDeploymentId, previousVersionName (the version a rollback restores) and canRollback, as in lexq_groups_get.",
|
|
926
933
|
inputSchema: z6.object({})
|
|
927
934
|
},
|
|
928
935
|
async () => callApi("GET", "deployments/overview")
|
|
@@ -958,14 +965,21 @@ function registerDeployTools(server, callApi) {
|
|
|
958
965
|
|
|
959
966
|
// src/mcp/tools/analytics.ts
|
|
960
967
|
import { z as z7 } from "zod";
|
|
961
|
-
import
|
|
968
|
+
import dedent3 from "dedent";
|
|
969
|
+
function datasetBlock(path) {
|
|
970
|
+
const dataset = { type: "UPLOADED", source: "S3_BUCKET", path };
|
|
971
|
+
return {
|
|
972
|
+
type: "text",
|
|
973
|
+
text: "Ready-to-use dataset block for lexq_simulation_start:\n" + stringifyJson({ dataset }, 2)
|
|
974
|
+
};
|
|
975
|
+
}
|
|
962
976
|
function registerAnalyticsTools(server, callApi) {
|
|
963
977
|
server.registerTool(
|
|
964
978
|
"lexq_dry_run",
|
|
965
979
|
{
|
|
966
980
|
title: "Dry Run",
|
|
967
981
|
annotations: { readOnlyHint: true },
|
|
968
|
-
description:
|
|
982
|
+
description: dedent3`
|
|
969
983
|
Execute a single dry run against a version. Tests how rules evaluate given input facts without side effects.
|
|
970
984
|
|
|
971
985
|
Returns:
|
|
@@ -995,7 +1009,7 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
995
1009
|
{
|
|
996
1010
|
title: "Dry Run Compare",
|
|
997
1011
|
annotations: { readOnlyHint: true },
|
|
998
|
-
description:
|
|
1012
|
+
description: dedent3`
|
|
999
1013
|
Compare dry run results between two versions using the same input facts. Useful for validating changes.
|
|
1000
1014
|
|
|
1001
1015
|
Returns:
|
|
@@ -1033,7 +1047,7 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
1033
1047
|
{
|
|
1034
1048
|
title: "Start Simulation",
|
|
1035
1049
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
1036
|
-
description:
|
|
1050
|
+
description: dedent3`
|
|
1037
1051
|
Start an Impact Simulation against historical, uploaded, or inline data.
|
|
1038
1052
|
|
|
1039
1053
|
dataset.type and dataset.source are BOTH required, and must be paired:
|
|
@@ -1138,13 +1152,17 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
1138
1152
|
{
|
|
1139
1153
|
title: "Upload Dataset",
|
|
1140
1154
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
1141
|
-
description:
|
|
1155
|
+
description: dedent3`
|
|
1142
1156
|
Upload inline CSV or JSON content as a simulation dataset.
|
|
1143
1157
|
The content is uploaded to S3 and a path is returned in the "path" field.
|
|
1144
1158
|
|
|
1145
1159
|
To use the returned path in lexq_simulation_start, set:
|
|
1146
1160
|
dataset: { "type": "UPLOADED", "source": "S3_BUCKET", "path": "<returned path>" }
|
|
1147
1161
|
|
|
1162
|
+
The content travels inside the tool call, so this suits small datasets, and the hosted server at
|
|
1163
|
+
mcp.lexq.io limits the size of a request. For a larger file, use lexq_dataset_upload_file on the
|
|
1164
|
+
local server (lexq serve --mcp), or upload it with the CLI or the console and pass the returned path.
|
|
1165
|
+
|
|
1148
1166
|
CSV example:
|
|
1149
1167
|
userId,paymentAmount
|
|
1150
1168
|
user_001,150000
|
|
@@ -1164,13 +1182,8 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
1164
1182
|
if (!result.isError) {
|
|
1165
1183
|
try {
|
|
1166
1184
|
const uploaded = parseJson(result.content[0]?.text ?? "{}");
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
result.content.push({
|
|
1170
|
-
type: "text",
|
|
1171
|
-
text: "Ready-to-use dataset block for lexq_simulation_start:\n" + stringifyJson({ dataset }, 2)
|
|
1172
|
-
});
|
|
1173
|
-
}
|
|
1185
|
+
const path = uploaded.data?.path ?? uploaded.path;
|
|
1186
|
+
if (typeof path === "string" && path) result.content.push(datasetBlock(path));
|
|
1174
1187
|
} catch {
|
|
1175
1188
|
}
|
|
1176
1189
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lexq/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"mcpName": "io.lexq/lexq",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "The Decision Operations Platform for engineering teams",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"lexq": "./dist/index.js"
|
|
@@ -44,6 +44,8 @@
|
|
|
44
44
|
"test:command-options": "node tests/command-options.mjs",
|
|
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
|
+
"test:rollback": "pnpm build && node tests/rollback.mjs",
|
|
48
|
+
"test:dataset-upload": "pnpm build && node tests/dataset-upload.mjs",
|
|
47
49
|
"prepublishOnly": "pnpm build",
|
|
48
50
|
"knip": "knip",
|
|
49
51
|
"format": "prettier --write \"src/**/*.ts\"",
|
|
@@ -57,7 +59,11 @@
|
|
|
57
59
|
"ai-agent",
|
|
58
60
|
"agent-skills",
|
|
59
61
|
"simulation",
|
|
60
|
-
"business-rules"
|
|
62
|
+
"business-rules",
|
|
63
|
+
"developer-tools",
|
|
64
|
+
"decision-management",
|
|
65
|
+
"mcp",
|
|
66
|
+
"mcp-server"
|
|
61
67
|
],
|
|
62
68
|
"author": "LexQ, LLC",
|
|
63
69
|
"license": "Apache-2.0",
|
|
@@ -71,16 +77,16 @@
|
|
|
71
77
|
},
|
|
72
78
|
"packageManager": "pnpm@10.28.0",
|
|
73
79
|
"dependencies": {
|
|
74
|
-
"@modelcontextprotocol/server": "^2.
|
|
80
|
+
"@modelcontextprotocol/server": "^2.3.1",
|
|
75
81
|
"cli-table3": "^0.6.5",
|
|
76
82
|
"commander": "^13.1.0",
|
|
77
83
|
"dedent": "^1.7.2",
|
|
78
84
|
"lossless-json": "4.3.1",
|
|
79
|
-
"zod": "^4.
|
|
85
|
+
"zod": "^4.6.7"
|
|
80
86
|
},
|
|
81
87
|
"devDependencies": {
|
|
82
88
|
"@eslint/js": "^9.20.0",
|
|
83
|
-
"@modelcontextprotocol/client": "^2.
|
|
89
|
+
"@modelcontextprotocol/client": "^2.3.1",
|
|
84
90
|
"@types/node": "^22.12.0",
|
|
85
91
|
"eslint": "^10.2.0",
|
|
86
92
|
"knip": "^6.29.0",
|
|
@@ -352,17 +352,24 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
|
|
|
352
352
|
**Goal:** Something went wrong in production — revert to the previous version.
|
|
353
353
|
|
|
354
354
|
```bash
|
|
355
|
-
# 1.
|
|
356
|
-
|
|
355
|
+
# 1. Read the group: currentVersionName is live now, previousVersionName is what a
|
|
356
|
+
# rollback restores, and a rollback is rejected while canRollback is false
|
|
357
|
+
lexq groups get --id <gid>
|
|
357
358
|
|
|
358
|
-
# 2.
|
|
359
|
-
lexq deploy rollback --group-id <gid> --memo "Reverting due to increased error rate"
|
|
359
|
+
# 2. Roll back with the IDs from step 1
|
|
360
|
+
lexq deploy rollback --group-id <gid> --memo "Reverting due to increased error rate" \
|
|
361
|
+
--expected-current-version-id <currentVersionId> \
|
|
362
|
+
--expected-current-deployment-id <currentDeploymentId>
|
|
360
363
|
|
|
361
364
|
# 3. Verify
|
|
362
365
|
lexq deploy overview
|
|
363
366
|
lexq history stats
|
|
364
367
|
```
|
|
365
368
|
|
|
369
|
+
If anyone deployed, rolled back or undeployed after step 1, step 2 is rejected with `409` and
|
|
370
|
+
nothing changes: `P-062` if another version or no version is live, `P-063` if the same version is
|
|
371
|
+
live but from a newer deployment. Start again from step 1.
|
|
372
|
+
|
|
366
373
|
---
|
|
367
374
|
|
|
368
375
|
## Recipe 7: Monitoring + Failure Triage
|
|
@@ -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
|