@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.
- package/dist/cli/commands/order.js +3 -1
- package/dist/cli/commands/trade.js +2 -1
- package/dist/cli/help.js +1 -1
- package/dist/core/client/capability-executor.js +81 -13
- package/dist/core/client/rapid-x-client.js +41 -8
- package/dist/core/client/symbol.js +6 -2
- package/dist/core/contracts/capabilities.js +6 -3
- package/dist/core/contracts/compatibility.js +1 -1
- package/dist/core/contracts/input-schema.js +66 -13
- package/dist/core/errors/product-error.js +2 -0
- package/dist/core/index.js +1 -0
- package/dist/core/safety/policy.js +6 -3
- package/dist/core/self-check/live-trading-verification-probes.js +12 -2
- package/dist/core/self-check/run-self-check.js +4 -2
- package/dist/core/trading/preview-preflight.js +338 -0
- package/dist/core/trading/preview.js +88 -14
- package/dist/core/trading/trading-verification.js +156 -26
- package/dist/core/version.js +1 -1
- package/dist/mcp/tool-registry.js +8 -1
- package/dist/mcp/tool-runner.js +29 -3
- package/package.json +1 -1
- package/packages/distribution/docs/cli.md +9 -1
- package/packages/distribution/docs/mcp.md +7 -2
- package/packages/distribution/docs/quickstart.md +9 -1
- package/packages/distribution/docs/tools.md +36 -2
- package/packages/distribution/manifests/offline-manifest.json +4 -4
- package/packages/distribution/registry/rapidx.mcp.json +1 -1
|
@@ -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:
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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({
|
|
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
|
|
114
|
+
return cleanupFailureReport(steps, preview.previewId, "cancel", undefined, "cancelOrder probe missing");
|
|
91
115
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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:
|
|
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
|
|
103
|
-
if (
|
|
104
|
-
steps.push({ name: "cleanup-check", status: "FAIL", toolOrCommandEvidence:
|
|
105
|
-
return
|
|
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:
|
|
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);
|
package/dist/core/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const RAPIDX_VERSION = "1.0.
|
|
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:
|
|
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"
|
package/dist/mcp/tool-runner.js
CHANGED
|
@@ -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:
|
|
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
|
@@ -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
|
-
-
|
|
34
|
-
- `rapidx/
|
|
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
|
|
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.
|
|
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:
|
|
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:
|
|
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:
|
|
22
|
+
"checksum": "sha256:e6ba0a1cbafa47fef2d6a2de6baffeb0a786d1d38886961c1347b9ffbd870e54",
|
|
23
23
|
"status": "ready"
|
|
24
24
|
}
|
|
25
25
|
]
|