@liquiditytech/rapidx-cli 1.0.27 → 1.0.29

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.
@@ -13,7 +13,7 @@ export async function runTradingVerification(rawInput, probes = {}) {
13
13
  }
14
14
  catch (error) {
15
15
  if (error instanceof ProductError) {
16
- return { submittedRealOrder: false, status: "FAIL", cleanupStatus: "NOT_VERIFIED", steps, errorCode: error.code, errorMessage: error.message };
16
+ return { submittedRealOrder: false, status: error.status, cleanupStatus: "NOT_VERIFIED", steps, errorCode: error.code, errorMessage: error.message, errorStage: "input-validation" };
17
17
  }
18
18
  throw error;
19
19
  }
@@ -26,20 +26,40 @@ export async function runTradingVerification(rawInput, probes = {}) {
26
26
  : selfCheckOptions);
27
27
  steps.push({ name: "self-check", status: selfCheck.status, toolOrCommandEvidence: "rapidx self-check --read-only --json" });
28
28
  if (selfCheck.status !== "PASS") {
29
- return { submittedRealOrder: false, status: "NOT_VERIFIED", cleanupStatus: "NOT_VERIFIED", steps, errorCode: "RCORE10002" };
29
+ return { submittedRealOrder: false, status: "NOT_VERIFIED", cleanupStatus: "NOT_VERIFIED", steps, errorCode: "RCORE10002", errorStage: "self-check" };
30
30
  }
31
- if (!input.explicitUserConsent) {
32
- steps.push({ name: "preview", status: "BLOCKED", toolOrCommandEvidence: "user consent missing for small real trade verification" });
33
- return { submittedRealOrder: false, status: "BLOCKED", cleanupStatus: "NOT_VERIFIED", steps, errorCode: "RCORE20001" };
31
+ const consentProblem = validateParameterBoundConsent(input);
32
+ if (consentProblem) {
33
+ steps.push({ name: "preview", status: "BLOCKED", toolOrCommandEvidence: consentProblem });
34
+ return {
35
+ submittedRealOrder: false,
36
+ status: "BLOCKED",
37
+ cleanupStatus: "NOT_VERIFIED",
38
+ steps,
39
+ errorCode: "RCORE20001",
40
+ errorMessage: consentProblem,
41
+ errorStage: "user-consent",
42
+ recommendedAction: "Ask the human user to confirm the exact symbol, side, positionSide when provided, maxNotional, real-order risk, and cancel cleanup behavior before running verify-live."
43
+ };
34
44
  }
35
45
  if (!probes.marketRules) {
36
46
  steps.push({ name: "market", status: "NOT_VERIFIED", toolOrCommandEvidence: "market rules probe missing" });
37
- return { submittedRealOrder: false, status: "NOT_VERIFIED", cleanupStatus: "NOT_VERIFIED", steps, errorCode: "RCORE10002" };
47
+ return { submittedRealOrder: false, status: "NOT_VERIFIED", cleanupStatus: "NOT_VERIFIED", steps, errorCode: "RCORE10002", errorStage: "market" };
38
48
  }
39
49
  const marketRules = await probes.marketRules(input);
40
50
  if (Number(marketRules.minNotional) > Number(input.maxNotional)) {
41
- steps.push({ name: "market", status: "BLOCKED", toolOrCommandEvidence: `minNotional=${marketRules.minNotional};maxNotional=${input.maxNotional}` });
42
- return { submittedRealOrder: false, status: "BLOCKED", cleanupStatus: "NOT_VERIFIED", steps, errorCode: "RCORE24001" };
51
+ const evidence = `minNotional=${marketRules.minNotional};maxNotional=${input.maxNotional}`;
52
+ steps.push({ name: "market", status: "BLOCKED", toolOrCommandEvidence: evidence });
53
+ return {
54
+ submittedRealOrder: false,
55
+ status: "BLOCKED",
56
+ cleanupStatus: "NOT_VERIFIED",
57
+ steps,
58
+ errorCode: "RCORE24001",
59
+ errorMessage: `Verification order blocked because ${evidence}.`,
60
+ errorStage: "market",
61
+ recommendedAction: `Increase maxNotional to at least ${marketRules.minNotional}, or choose a symbol whose minNotional is within the authorized maxNotional.`
62
+ };
43
63
  }
44
64
  steps.push({ name: "market", status: "PASS", toolOrCommandEvidence: `minNotional=${marketRules.minNotional};tickSize=${marketRules.tickSize}` });
45
65
  const capability = findCapabilityById("order.preview");
@@ -60,18 +80,18 @@ export async function runTradingVerification(rawInput, probes = {}) {
60
80
  steps.push({ name: "preview", status: "PASS", toolOrCommandEvidence: preview.previewId });
61
81
  if (!probes.placeOrder) {
62
82
  steps.push({ name: "place", status: "NOT_VERIFIED", toolOrCommandEvidence: "placeOrder probe missing" });
63
- return { submittedRealOrder: false, status: "NOT_VERIFIED", cleanupStatus: "NOT_VERIFIED", steps, previewId: preview.previewId, errorCode: "RCORE23001" };
83
+ return { submittedRealOrder: false, status: "NOT_VERIFIED", cleanupStatus: "NOT_VERIFIED", steps, previewId: preview.previewId, errorCode: "RCORE23001", errorStage: "place" };
64
84
  }
65
85
  const placed = await probes.placeOrder({ ...input, previewId: preview.previewId, orderType: "LIMIT", postOnly: true, price, quantity });
66
- steps.push({ name: "place", status: "PASS", toolOrCommandEvidence: placed.requestId ?? placed.orderId });
86
+ steps.push({ name: "place", status: "PASS", toolOrCommandEvidence: [placed.requestId ?? placed.orderId, input.positionSide ? `positionSide=${input.positionSide}` : undefined].filter(Boolean).join(";") });
67
87
  if (!probes.queryOrder) {
68
88
  steps.push({ name: "query", status: "NOT_VERIFIED", toolOrCommandEvidence: "queryOrder probe missing" });
69
- return { submittedRealOrder: true, status: "NOT_VERIFIED", cleanupStatus: "NOT_VERIFIED", steps, previewId: preview.previewId, errorCode: "RCORE23001" };
89
+ return { submittedRealOrder: true, status: "NOT_VERIFIED", cleanupStatus: "NOT_VERIFIED", steps, previewId: preview.previewId, errorCode: "RCORE23001", errorStage: "query" };
70
90
  }
71
91
  const queried = await probes.queryOrder(placed);
72
92
  if (queried.status === "UNKNOWN") {
73
93
  steps.push({ name: "query", status: "NOT_VERIFIED", toolOrCommandEvidence: queried.orderId });
74
- return { submittedRealOrder: true, status: "NOT_VERIFIED", cleanupStatus: "NOT_VERIFIED", steps, previewId: preview.previewId, errorCode: "RCORE23001" };
94
+ return { submittedRealOrder: true, status: "NOT_VERIFIED", cleanupStatus: "NOT_VERIFIED", steps, previewId: preview.previewId, errorCode: "RCORE23001", errorStage: "query", lastObservedOrderState: queried.status };
75
95
  }
76
96
  steps.push({ name: "query", status: "PASS", toolOrCommandEvidence: `${queried.orderId}:${queried.status}` });
77
97
  let currentOrder = queried;
@@ -79,32 +99,37 @@ export async function runTradingVerification(rawInput, probes = {}) {
79
99
  currentOrder = await probes.amendOrder(currentOrder);
80
100
  steps.push({ name: "amend", status: currentOrder.status === "UNKNOWN" ? "NOT_VERIFIED" : "PASS", toolOrCommandEvidence: `${currentOrder.orderId}:${currentOrder.status}` });
81
101
  if (currentOrder.status === "UNKNOWN") {
82
- return { submittedRealOrder: true, status: "NOT_VERIFIED", cleanupStatus: "NOT_VERIFIED", steps, previewId: preview.previewId, errorCode: "RCORE23001" };
102
+ return { submittedRealOrder: true, status: "NOT_VERIFIED", cleanupStatus: "NOT_VERIFIED", steps, previewId: preview.previewId, errorCode: "RCORE23001", errorStage: "amend", lastObservedOrderState: currentOrder.status };
83
103
  }
84
104
  }
85
105
  else {
86
- steps.push({ name: "amend", status: "EXPECTED_ERROR", toolOrCommandEvidence: "amendOrder probe not supported" });
106
+ steps.push({
107
+ name: "amend",
108
+ status: "EXPECTED_ERROR",
109
+ toolOrCommandEvidence: "optional verify-live amend check skipped; order.amend remains available outside verify-live"
110
+ });
87
111
  }
88
112
  if (!probes.cancelOrder) {
89
113
  steps.push({ name: "cancel", status: "FAIL", toolOrCommandEvidence: "cancelOrder probe missing" });
90
- return { submittedRealOrder: true, status: "FAIL", cleanupStatus: "FAIL", steps, previewId: preview.previewId, errorCode: "RCORE24002" };
114
+ return cleanupFailureReport(steps, preview.previewId, "cancel", undefined, "cancelOrder probe missing");
91
115
  }
92
- currentOrder = await probes.cancelOrder(currentOrder);
93
- if (currentOrder.status !== "CANCELED") {
94
- steps.push({ name: "cancel", status: "FAIL", toolOrCommandEvidence: `${currentOrder.orderId}:${currentOrder.status}` });
95
- return { submittedRealOrder: true, status: "FAIL", cleanupStatus: "FAIL", steps, previewId: preview.previewId, errorCode: "RCORE24002" };
116
+ const cancelConfirmation = await confirmCancel(currentOrder, probes);
117
+ currentOrder = cancelConfirmation.order;
118
+ if (!cancelConfirmation.confirmed) {
119
+ steps.push({ name: "cancel", status: "FAIL", toolOrCommandEvidence: cancelConfirmation.evidence });
120
+ return cleanupFailureReport(steps, preview.previewId, "cancel-confirmation", currentOrder.status);
96
121
  }
97
- steps.push({ name: "cancel", status: "PASS", toolOrCommandEvidence: `${currentOrder.orderId}:${currentOrder.status}` });
122
+ steps.push({ name: "cancel", status: "PASS", toolOrCommandEvidence: cancelConfirmation.evidence });
98
123
  if (!probes.cleanupCheck) {
99
124
  steps.push({ name: "cleanup-check", status: "NOT_VERIFIED", toolOrCommandEvidence: "cleanupCheck probe missing" });
100
- return { submittedRealOrder: true, status: "NOT_VERIFIED", cleanupStatus: "NOT_VERIFIED", steps, previewId: preview.previewId, errorCode: "RCORE24002" };
125
+ return { submittedRealOrder: true, status: "NOT_VERIFIED", cleanupStatus: "NOT_VERIFIED", steps, previewId: preview.previewId, errorCode: "RCORE24002", errorStage: "cleanup-check", recommendedAction: "Query order/list and position/list before retrying verify-live." };
101
126
  }
102
- const cleanup = await probes.cleanupCheck(currentOrder);
103
- if (cleanup.openOrders !== 0 || cleanup.unexpectedPositions !== 0) {
104
- steps.push({ name: "cleanup-check", status: "FAIL", toolOrCommandEvidence: `openOrders=${cleanup.openOrders};unexpectedPositions=${cleanup.unexpectedPositions}` });
105
- return { submittedRealOrder: true, status: "FAIL", cleanupStatus: "FAIL", steps, previewId: preview.previewId, errorCode: "RCORE24002" };
127
+ const cleanupConfirmation = await confirmCleanup(currentOrder, probes);
128
+ if (!cleanupConfirmation.confirmed) {
129
+ steps.push({ name: "cleanup-check", status: "FAIL", toolOrCommandEvidence: cleanupConfirmation.evidence });
130
+ return cleanupFailureReport(steps, preview.previewId, "cleanup-check", currentOrder.status, `cleanup still shows ${cleanupEvidence(cleanupConfirmation.cleanup)}`);
106
131
  }
107
- steps.push({ name: "cleanup-check", status: "PASS", toolOrCommandEvidence: "openOrders=0;unexpectedPositions=0" });
132
+ steps.push({ name: "cleanup-check", status: "PASS", toolOrCommandEvidence: cleanupConfirmation.evidence });
108
133
  return {
109
134
  submittedRealOrder: true,
110
135
  status: "PASS",
@@ -125,8 +150,113 @@ function normalizeTradingVerificationInput(input) {
125
150
  if (typeof input.acceptedRiskText === "string") {
126
151
  normalized.acceptedRiskText = input.acceptedRiskText;
127
152
  }
153
+ if (input.positionSide === "LONG" || input.positionSide === "SHORT") {
154
+ normalized.positionSide = input.positionSide;
155
+ }
128
156
  return normalized;
129
157
  }
158
+ function validateParameterBoundConsent(input) {
159
+ if (!input.explicitUserConsent) {
160
+ return "user consent missing for small real trade verification";
161
+ }
162
+ const text = input.acceptedRiskText?.trim() ?? "";
163
+ const upperText = text.toUpperCase();
164
+ const lowerText = text.toLowerCase();
165
+ const missing = [];
166
+ if (text.length < 20) {
167
+ missing.push("acceptedRiskText");
168
+ }
169
+ if (!upperText.includes(input.symbol.toUpperCase())) {
170
+ missing.push("symbol");
171
+ }
172
+ if (!upperText.includes(input.side)) {
173
+ missing.push("side");
174
+ }
175
+ if (input.positionSide && !upperText.includes(input.positionSide)) {
176
+ missing.push("positionSide");
177
+ }
178
+ if (!text.includes(input.maxNotional)) {
179
+ missing.push("maxNotional");
180
+ }
181
+ if (!lowerText.includes("real") && !text.includes("真实")) {
182
+ missing.push("real-order risk");
183
+ }
184
+ if (!lowerText.includes("cancel") && !lowerText.includes("cleanup") && !text.includes("取消") && !text.includes("清理")) {
185
+ missing.push("cancel cleanup");
186
+ }
187
+ if (missing.length > 0) {
188
+ return `acceptedRiskText must include ${missing.join(", ")} for this exact verify-live request`;
189
+ }
190
+ return undefined;
191
+ }
192
+ async function confirmCancel(order, probes) {
193
+ const observations = [];
194
+ let current = await probes.cancelOrder(order);
195
+ observations.push(current.status);
196
+ if (isSafeCancelTerminalState(current.status)) {
197
+ return { confirmed: true, order: current, evidence: `${current.orderId}:${observations.join("->")}` };
198
+ }
199
+ const delays = probes.cancelConfirmationDelaysMs ?? [100, 250, 500, 1_000, 2_000];
200
+ const wait = probes.wait ?? defaultWait;
201
+ for (const delay of delays) {
202
+ await wait(delay);
203
+ if (!probes.queryOrder) {
204
+ break;
205
+ }
206
+ current = await probes.queryOrder(current);
207
+ observations.push(current.status);
208
+ if (isSafeCancelTerminalState(current.status)) {
209
+ return { confirmed: true, order: current, evidence: `${current.orderId}:${observations.join("->")}` };
210
+ }
211
+ }
212
+ return { confirmed: false, order: current, evidence: `${current.orderId}:${observations.join("->")}` };
213
+ }
214
+ function isSafeCancelTerminalState(status) {
215
+ return status === "CANCELED" || status === "EXPIRED" || status === "REJECTED";
216
+ }
217
+ async function confirmCleanup(order, probes) {
218
+ const observations = [];
219
+ let current = await probes.cleanupCheck(order);
220
+ observations.push(cleanupEvidence(current));
221
+ if (isCleanupClean(current)) {
222
+ return { confirmed: true, cleanup: current, evidence: observations.join("->") };
223
+ }
224
+ const delays = probes.cleanupCheckDelaysMs ?? probes.cancelConfirmationDelaysMs ?? [100, 250, 500, 1_000, 2_000];
225
+ const wait = probes.wait ?? defaultWait;
226
+ for (const delay of delays) {
227
+ await wait(delay);
228
+ current = await probes.cleanupCheck(order);
229
+ observations.push(cleanupEvidence(current));
230
+ if (isCleanupClean(current)) {
231
+ return { confirmed: true, cleanup: current, evidence: observations.join("->") };
232
+ }
233
+ }
234
+ return { confirmed: false, cleanup: current, evidence: observations.join("->") };
235
+ }
236
+ function isCleanupClean(cleanup) {
237
+ return cleanup.openOrders === 0 && cleanup.unexpectedPositions === 0;
238
+ }
239
+ function cleanupEvidence(cleanup) {
240
+ return `openOrders=${cleanup.openOrders};unexpectedPositions=${cleanup.unexpectedPositions}`;
241
+ }
242
+ function defaultWait(milliseconds) {
243
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
244
+ }
245
+ function cleanupFailureReport(steps, previewId, errorStage, lastObservedOrderState, detail) {
246
+ return {
247
+ submittedRealOrder: true,
248
+ status: "FAIL",
249
+ cleanupStatus: "FAIL",
250
+ steps,
251
+ previewId,
252
+ errorCode: "RCORE24002",
253
+ errorMessage: detail ?? "Trading verification cleanup could not be confirmed.",
254
+ errorStage,
255
+ ...(lastObservedOrderState ? { lastObservedOrderState } : {}),
256
+ expectedOrderStates: ["CANCELED", "EXPIRED", "REJECTED"],
257
+ recommendedAction: "Query order/get, order/list, and position/list before retrying; do not submit another verification until cleanup is confirmed."
258
+ };
259
+ }
130
260
  function quantityForNotional(minNotional, price) {
131
261
  const priceNumber = Number(price);
132
262
  const minNotionalNumber = Number(minNotional);
@@ -1 +1 @@
1
- export const RAPIDX_VERSION = "1.0.27";
1
+ export const RAPIDX_VERSION = "1.0.29";
@@ -10,13 +10,20 @@ export function getMcpToolDefinitions() {
10
10
  const canonical = capabilityForTool(capability.mcpTool);
11
11
  tools.push({
12
12
  name: capability.mcpTool,
13
- description: `RapidX ${canonical.capabilityId} (${canonical.riskLevel}, schema ${SCHEMA_VERSION})`,
13
+ description: toolDescription(canonical),
14
14
  inputSchema: inputSchemaForName(canonical.inputSchema),
15
15
  capability: canonical
16
16
  });
17
17
  }
18
18
  return tools.sort((a, b) => a.name.localeCompare(b.name));
19
19
  }
20
+ function toolDescription(capability) {
21
+ const base = `RapidX ${capability.capabilityId} (${capability.riskLevel}, schema ${SCHEMA_VERSION})`;
22
+ if (!capability.containsRealOrder) {
23
+ return base;
24
+ }
25
+ return `${base}. This submits a real verification order and requires explicit human confirmation bound to symbol, side, maxNotional, and cancel cleanup behavior.`;
26
+ }
20
27
  export function capabilityForTool(toolName) {
21
28
  const overrideId = toolName === "rapidx/tools"
22
29
  ? "schema.discover"
@@ -1,4 +1,4 @@
1
- import { consumePreview, createTargetedTradePreview, createTradePreview, defaultSafetyPolicy, executeRapidXCapability, findCapabilityById, getSchemaResult, makeEvidence, makePreviewStore, makeSafetyState, normalizeUnknownError, runSelfCheck, runTradingVerification, verifyPreview, buildLiveReadOnlySelfCheck, buildLiveTradingVerificationProbes, checkForUpdate, } from "../core/index.js";
1
+ import { consumePreview, createTargetedTradePreview, createTradePreview, defaultSafetyPolicy, executeRapidXCapability, findCapabilityById, getSchemaResult, makeEvidence, makePreviewStore, makeSafetyState, normalizeUnknownError, runSelfCheck, runPreviewPreflight, runTradingVerification, verifyPreview, buildLiveReadOnlySelfCheck, buildLiveTradingVerificationProbes, checkForUpdate, } from "../core/index.js";
2
2
  import { capabilityForTool, getMcpToolDefinitions } from "./tool-registry.js";
3
3
  import { writeMcpAudit } from "./audit.js";
4
4
  const previewStore = makePreviewStore();
@@ -6,14 +6,19 @@ const safetyState = makeSafetyState();
6
6
  export async function runMcpTool(toolName, input = {}) {
7
7
  try {
8
8
  if (toolName === "rapidx/tools") {
9
+ const schema = getSchemaResult();
9
10
  const data = {
10
- schemaVersion: getSchemaResult().schemaVersion,
11
+ schemaVersion: schema.schemaVersion,
12
+ inputSchemas: schema.inputSchemas,
11
13
  tools: getMcpToolDefinitions().map((tool) => ({
12
14
  name: tool.name,
13
15
  riskLevel: tool.capability.riskLevel,
14
16
  inputSchema: tool.capability.inputSchema,
15
17
  outputSchema: tool.capability.outputSchema,
16
- previewRequired: tool.capability.previewRequired
18
+ previewRequired: tool.capability.previewRequired,
19
+ containsRealOrder: tool.capability.containsRealOrder === true,
20
+ requiresExplicitHumanConfirmation: tool.capability.requiresExplicitHumanConfirmation === true,
21
+ confirmationMode: tool.capability.confirmationMode
17
22
  }))
18
23
  };
19
24
  const auditId = writeMcpAudit("tools/list", "PASS", { toolName });
@@ -52,6 +57,13 @@ export async function runMcpTool(toolName, input = {}) {
52
57
  throw new Error("order.preview capability missing");
53
58
  }
54
59
  const preview = createTradePreview(previewCapability, input, { ...defaultSafetyPolicy(), tradingEnabled: true, readOnly: false }, safetyState, previewStore);
60
+ try {
61
+ Object.assign(preview, await runPreviewPreflight("order.place", input));
62
+ }
63
+ catch (error) {
64
+ previewStore.records.delete(preview.previewId);
65
+ throw error;
66
+ }
55
67
  const auditId = writeMcpAudit("trade-preview", "PASS", { toolName, capabilityId: capability.capabilityId, previewId: preview.previewId });
56
68
  return { ok: true, status: "PASS", data: preview, auditId, evidence: [makeEvidence(toolName)] };
57
69
  }
@@ -62,6 +74,13 @@ export async function runMcpTool(toolName, input = {}) {
62
74
  throw new Error(`${concretePreviewTarget} capability missing`);
63
75
  }
64
76
  const preview = createTargetedTradePreview(targetCapability, input, { ...defaultSafetyPolicy(), tradingEnabled: true, readOnly: false }, safetyState, previewStore);
77
+ try {
78
+ Object.assign(preview, await runPreviewPreflight(concretePreviewTarget, input));
79
+ }
80
+ catch (error) {
81
+ previewStore.records.delete(preview.previewId);
82
+ throw error;
83
+ }
65
84
  const auditId = writeMcpAudit("trade-preview", "PASS", { toolName, capabilityId: targetCapability.capabilityId, previewId: preview.previewId });
66
85
  return { ok: true, status: "PASS", data: preview, auditId, evidence: [makeEvidence(toolName)] };
67
86
  }
@@ -75,6 +94,13 @@ export async function runMcpTool(toolName, input = {}) {
75
94
  throw new Error("trade preview target must be a non-preview trade write capability.");
76
95
  }
77
96
  const preview = createTargetedTradePreview(targetCapability, input, { ...defaultSafetyPolicy(), tradingEnabled: true, readOnly: false }, safetyState, previewStore);
97
+ try {
98
+ Object.assign(preview, await runPreviewPreflight(targetCapability.capabilityId, input));
99
+ }
100
+ catch (error) {
101
+ previewStore.records.delete(preview.previewId);
102
+ throw error;
103
+ }
78
104
  const auditId = writeMcpAudit("trade-preview", "PASS", { toolName, capabilityId: targetCapability.capabilityId, previewId: preview.previewId });
79
105
  return { ok: true, status: "PASS", data: preview, auditId, evidence: [makeEvidence(toolName)] };
80
106
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liquiditytech/rapidx-cli",
3
- "version": "1.0.27",
3
+ "version": "1.0.29",
4
4
  "description": "RapidX CLI, MCP server mode, registry, and release assets.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -21,7 +21,9 @@ Supported conventions:
21
21
  - Use `rapidx order cancel-preview` for `order.cancel`.
22
22
  - Use `rapidx trade preview` with `targetCapabilityId` for non-order trade writes, including position, account, and algo writes.
23
23
  - `rapidx trade preview` input is flat JSON; do not wrap target parameters under `params`.
24
+ - CLI preview ids are stored in the local CLI preview store. Do not submit MCP-created preview ids through CLI.
24
25
  - Treat `maxNotional` as a safety upper bound, not as the target order amount. Check symbol `minNotional` before increasing a requested amount.
26
+ - Use `positionSide="LONG"` or `positionSide="SHORT"` for hedge-mode order placement or verification. Do not switch account position mode just to place a hedge-side order.
25
27
 
26
28
  Examples:
27
29
 
@@ -42,8 +44,14 @@ rapidx account set-position-mode --input @/absolute/path/set-position-mode.json
42
44
 
43
45
  `rapidx update check --json` reads the RapidX release manifest and caches the result locally. Use it during setup, review, or session startup. Trade submit paths should not perform a fresh network update check.
44
46
 
47
+ `rapidx schema --json` returns `capabilities` plus `inputSchemas`. Agents should read the concrete input schema before constructing write inputs.
48
+
45
49
  `rapidx account balance` defaults to `mode=portfolio` and reads `/api/v1/trading/portfolio/assets`. `mode=account` reads `/api/v1/account/balance` and requires credentials with account-level permission.
46
50
 
47
- `rapidx account set-position-mode` is a trade write. It requires a matching preview token and a `continueConsentId` before execution.
51
+ `rapidx account set-position-mode` is a trade write. Use it only when the user explicitly asks to change account position mode. It requires a matching preview token and a `continueConsentId` before execution.
52
+
53
+ `rapidx order cancel` is asynchronous. A successful response can mean cancel accepted but not terminal. Poll `rapidx order get --json` when `terminalStateConfirmed=false`.
54
+
55
+ Automation mode still uses preview. Pass `automationMode=true` and the user's exact `automationConsentText` to preview only when the user has enabled automation in chat for the current scope.
48
56
 
49
57
  `rapidx position close` is a close-position action. Do not pass `side` or `quantity`; RapidX determines BUY or SELL from the current position and closes the target symbol/positionSide. Use a reduce-only order flow for partial closes. Verify the final exposure with `rapidx position list --json`, and do not rely only on `order get` to interpret the close intent.
@@ -24,13 +24,18 @@ Public tool names use the `rapidx/` prefix.
24
24
 
25
25
  Key trading tools:
26
26
 
27
+ - `rapidx/tools` returns the MCP tool list plus `inputSchemas`. Agents should read the concrete input schema before constructing write inputs.
27
28
  - `rapidx/update/check` reads the RapidX release manifest and returns current/latest CLI version, minimum write version, skills update advice, and upgrade commands.
28
29
  - `rapidx/self-check` can include update state when called with `{"checkUpdates": true}`.
29
30
  - `rapidx/order/preview` creates preview tokens for `order.place`.
30
31
  - `rapidx/order/place-preview`, `rapidx/order/amend-preview`, and `rapidx/order/cancel-preview` are concrete order preview aliases. Preview responses include `confirmation.submitToken`; pass it as `continueConsentId` when submitting the matching write tool.
31
32
  - `rapidx/trade/preview` creates preview tokens for non-place trade writes by `targetCapabilityId`, including position, account, and algo writes.
33
+ - Preview ids are local to the running MCP server. Do not submit a CLI-created preview id through MCP, and do not submit an MCP-created preview id through CLI.
34
+ - `rapidx/order/cancel` returns cancel acceptance plus terminal-state guidance. Poll `rapidx/order/get` when `terminalStateConfirmed=false`.
35
+ - Automation mode still uses preview. Set `automationMode=true` and pass the user's exact `automationConsentText` only when the user has enabled automation in chat for the current scope.
32
36
  - `rapidx/position/history` reads historical positions.
33
- - `rapidx/account/set-position-mode` changes account position mode and requires preview plus explicit continuation consent.
34
- - `rapidx/trade/verify-live` runs the optional small real-trade verification flow with `explicitUserConsent`; it creates its own internal preview and does not accept an external `previewId`. `rapidx/trading-verification` remains supported as a compatibility alias.
37
+ - Hedge-mode order placement and verification use `positionSide="LONG"` or `positionSide="SHORT"` in the order or verify-live input. Do not use account mode switching as a substitute for order-level `positionSide`.
38
+ - `rapidx/account/set-position-mode` changes account position mode and requires preview plus explicit continuation consent. Use it only when the user explicitly asks to change account position mode.
39
+ - `rapidx/trade/verify-live` runs the optional small real-trade verification flow with `explicitUserConsent` and parameter-bound `acceptedRiskText`; it creates its own internal preview and does not accept an external `previewId`. If `positionSide` is provided, `acceptedRiskText` must include that position side. `rapidx/trading-verification` remains supported as a compatibility alias.
35
40
 
36
41
  Agents must discover the full tool list through `rapidx/tools` and must not invent tool names.
@@ -113,6 +113,8 @@ rapidx schema --json
113
113
 
114
114
  For trade writes, create a preview before submit. Use `rapidx order place-preview` for `order.place`, `rapidx order amend-preview` for `order.amend`, and `rapidx order cancel-preview` for `order.cancel`. Use flat JSON with `rapidx trade preview` and `targetCapabilityId` for non-order writes such as `position.set-leverage`, `position.close`, or `account.set-position-mode`.
115
115
 
116
+ `rapidx schema --json` and `rapidx/tools` return concrete `inputSchemas`. For hedge-mode orders, use `positionSide="LONG"` or `positionSide="SHORT"` in the order or verify-live input. Use `account.set-position-mode` only when the user explicitly asks to change account mode.
117
+
116
118
  For clearer order flows, prefer the explicit aliases:
117
119
 
118
120
  ```bash
@@ -124,6 +126,12 @@ rapidx trade preview --input '{"targetCapabilityId":"position.set-leverage","sym
124
126
 
125
127
  When submitting the actual write, pass the returned `previewId` and use `confirmation.submitToken` as `continueConsentId`.
126
128
 
129
+ Preview ids are runtime-local. Submit MCP previews through the same MCP server runtime, and submit CLI previews through the same CLI preview store.
130
+
131
+ `order.cancel` is asynchronous. If the cancel result says `terminalStateConfirmed=false`, poll `order get` until a terminal state before claiming that the order is cancelled.
132
+
133
+ For automation, keep the preview-first flow. Use `automationMode=true` and the user's exact `automationConsentText` only after the user has enabled RapidX automation mode in chat for the current scope.
134
+
127
135
  `maxNotional` is a safety upper bound, not the target order amount. If a requested amount is below the symbol `minNotional`, ask the user to confirm the larger amount before submitting. For `position.close`, do not pass `side` or `quantity`; RapidX determines the close side from the current position and closes the target symbol/positionSide. Use a reduce-only order flow for partial closes, then check final exposure with `position list`.
128
136
 
129
- Use `rapidx trade verify-live --json` only when the user explicitly authorizes a small real-trade verification. The older `rapidx self-check trade-verify --json` command remains supported for compatibility.
137
+ Use `rapidx trade verify-live --json` only when the user explicitly authorizes a small real-trade verification. Include `acceptedRiskText` that names the exact symbol, side, positionSide when provided, maxNotional, real-order risk, and cancel cleanup behavior. The older `rapidx self-check trade-verify --json` command remains supported for compatibility.
@@ -4,7 +4,7 @@ The canonical source is `rapidx schema --json` or `rapidx/tools`.
4
4
 
5
5
  ## Discovery And Update
6
6
 
7
- - `rapidx/tools`: returns the MCP tool surface and schema metadata.
7
+ - `rapidx/tools`: returns the MCP tool surface and concrete `inputSchemas`.
8
8
  - `rapidx/self-check`: verifies discovery, schema, credentials, and optional read-only probes. Pass `checkUpdates=true` to include release manifest status.
9
9
  - `rapidx/update/check`: reads the RapidX release manifest, returns version status, minimum write version, skills update advice, and upgrade commands.
10
10
 
@@ -24,12 +24,46 @@ The canonical source is `rapidx schema --json` or `rapidx/tools`.
24
24
  - Position reads: list and position history.
25
25
  - Algo reads: list.
26
26
 
27
+ ## Symbol Format
28
+
29
+ Use RapidX symbols in tool inputs:
30
+
31
+ ```text
32
+ BINANCE_PERP_<BASE>_<QUOTE>
33
+ ```
34
+
35
+ `OKX_PERP_<BASE>_<QUOTE>` is also supported for OKX perpetual instruments. `OKX_SWAP_<BASE>_<QUOTE>` is accepted as an input alias and is normalized to `OKX_PERP_<BASE>_<QUOTE>` in preview matching and outputs.
36
+
37
+ Public market adapters may call venue APIs with official venue symbols such as `BTCUSDT` or `BTC-USDT-SWAP`. When the venue response uses a different symbol, RapidX returns canonical `symbol` plus `originalSymbol`.
38
+
27
39
  ## Write Tools
28
40
 
29
41
  - Order writes: place, amend, and cancel.
30
42
  - Position writes: close and set leverage.
31
43
  - Account writes: set position mode.
32
44
  - Algo writes: place, amend, and cancel.
33
- - Live verification: `rapidx/trade/verify-live` is the clearer alias for the small real-trade verification flow. `rapidx/trading-verification` remains supported for compatibility.
45
+ - Live verification: `rapidx/trade/verify-live` is the clearer alias for the small real-trade verification flow. It submits a real verification order and requires `acceptedRiskText` bound to the exact symbol, side, maxNotional, real-order risk, and cancel cleanup behavior. If `positionSide` is provided, `acceptedRiskText` must include it. `rapidx/trading-verification` remains supported for compatibility.
34
46
 
35
47
  All write tools require preview where the schema marks `previewRequired: true`.
48
+ MARKET order writes are allowed after preview and consent. Preview includes immediate-execution and slippage risk notes; do not replace MARKET with a synthetic limit strategy unless the user explicitly asks for that order style.
49
+
50
+ For hedge-mode order placement, pass `positionSide="LONG"` or `positionSide="SHORT"` on the order or verify-live input. Use `account.set-position-mode` only when the user explicitly asks to change the account position mode.
51
+
52
+ Preview ids are runtime-local. A preview created by MCP must be submitted through the same MCP server runtime. A preview created by CLI must be submitted through the same CLI preview store. Do not mix MCP preview ids with CLI submit commands, or the reverse.
53
+
54
+ `order.cancel` is asynchronous. A successful cancel response means the cancel request was accepted; it may not prove a terminal order state yet. The response includes `cancelAccepted`, `terminalStateConfirmed`, `lastObservedOrderState`, and `recommendedAction`. Poll `order.get` until `CANCELED`, `REJECTED`, `EXPIRED`, or timeout when `terminalStateConfirmed=false`.
55
+
56
+ For automation, keep preview-first execution. Set `automationMode=true` and pass the user's exact `automationConsentText` only when the user has explicitly enabled RapidX automation mode in chat. The preview response still returns `confirmation.submitToken`; automation mode only means the agent may use that submit token without asking for another per-order chat confirmation within the authorized scope.
57
+
58
+ For TPSL or conditional algo orders, use `rapidx/trade/preview` with `targetCapabilityId="algo.place"`, then submit `rapidx/algo/place`. `conditionType="ENTIRE_CLOSE_POSITION"` may use `orderType="MARKET"` without `quantity` or `amount`; provide at least one take-profit or stop-loss trigger and verify with `rapidx/algo/list`.
59
+
60
+ ## Result Statuses
61
+
62
+ - `PASS`: the tool or command completed successfully.
63
+ - `INVALID_INPUT`: local schema or parameter validation failed. Fix the input before retrying.
64
+ - `BLOCKED`: local preview, safety, compatibility, or policy checks rejected the action before submission.
65
+ - `NOT_FOUND`: the requested order, position, or upstream resource was not found.
66
+ - `PERMISSION_SCOPE_ERROR`: credentials are valid but do not cover the requested scope, such as account-level balance with portfolio-scoped credentials.
67
+ - `BUSINESS_ERROR`: RapidX or venue business rules rejected the request.
68
+ - `NOT_VERIFIED`: the tool could not prove the requested state.
69
+ - `FAIL`: startup, auth, network, malformed response, or unexpected execution failure.
@@ -1,25 +1,25 @@
1
1
  {
2
- "version": "1.0.27",
2
+ "version": "1.0.29",
3
3
  "artifacts": [
4
4
  {
5
5
  "name": "rapidx-mcp-registry",
6
6
  "path": "packages/distribution/registry/rapidx.mcp.json",
7
7
  "channel": "offline",
8
- "checksum": "sha256:b48359c7cf540918a6de44a08378ccb5c9a6746e98112e20d6c586e660752b0a",
8
+ "checksum": "sha256:8a462f111ce2e1496b670978113277deeb49fc8ebcc9bfb2f9af3a91e5c8f9eb",
9
9
  "status": "ready"
10
10
  },
11
11
  {
12
12
  "name": "rapidx-quickstart-doc",
13
13
  "path": "packages/distribution/docs/quickstart.md",
14
14
  "channel": "offline",
15
- "checksum": "sha256:7d8f511a5dbe803e7a19ede0be79f6bf1b77942dbafa45d0ce87fb3b098f7829",
15
+ "checksum": "sha256:f1771cbc6e3da39b47afac839ca89fc063bede11ac9d8078b215145953c0145c",
16
16
  "status": "ready"
17
17
  },
18
18
  {
19
19
  "name": "rapidx-tools-doc",
20
20
  "path": "packages/distribution/docs/tools.md",
21
21
  "channel": "offline",
22
- "checksum": "sha256:ee6adae99a6facfd5f1c3dced86c7a28eafd11ecbe7e2a7eda159a6aa788a245",
22
+ "checksum": "sha256:e6ba0a1cbafa47fef2d6a2de6baffeb0a786d1d38886961c1347b9ffbd870e54",
23
23
  "status": "ready"
24
24
  }
25
25
  ]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rapidx",
3
3
  "packageName": "@liquiditytech/rapidx-cli",
4
- "version": "1.0.27",
4
+ "version": "1.0.29",
5
5
  "command": "rapidx",
6
6
  "args": ["mcp", "serve"],
7
7
  "launchCommand": "rapidx",