@liquiditytech/rapidx-cli 1.0.26
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/README.md +81 -0
- package/dist/cli/audit.js +10 -0
- package/dist/cli/bin.js +41 -0
- package/dist/cli/commands/account.js +34 -0
- package/dist/cli/commands/algo.js +35 -0
- package/dist/cli/commands/auth.js +22 -0
- package/dist/cli/commands/config.js +46 -0
- package/dist/cli/commands/doctor.js +42 -0
- package/dist/cli/commands/index.js +73 -0
- package/dist/cli/commands/market.js +26 -0
- package/dist/cli/commands/order.js +81 -0
- package/dist/cli/commands/position.js +35 -0
- package/dist/cli/commands/schema.js +5 -0
- package/dist/cli/commands/self-check.js +24 -0
- package/dist/cli/commands/trade-gate.js +26 -0
- package/dist/cli/commands/trade.js +30 -0
- package/dist/cli/commands/update.js +27 -0
- package/dist/cli/envelope.js +29 -0
- package/dist/cli/help.js +34 -0
- package/dist/cli/invocation-checker.js +39 -0
- package/dist/cli/mcp-entry.js +4 -0
- package/dist/cli/parser.js +87 -0
- package/dist/core/audit/redaction.js +56 -0
- package/dist/core/audit/writer.js +27 -0
- package/dist/core/client/capability-executor.js +400 -0
- package/dist/core/client/rapid-x-client.js +156 -0
- package/dist/core/client/signing.js +24 -0
- package/dist/core/client/symbol.js +36 -0
- package/dist/core/config/credential.js +42 -0
- package/dist/core/config/resolve.js +24 -0
- package/dist/core/contracts/capabilities.js +77 -0
- package/dist/core/contracts/compatibility.js +65 -0
- package/dist/core/contracts/events.js +29 -0
- package/dist/core/contracts/evidence.js +7 -0
- package/dist/core/contracts/input-schema.js +370 -0
- package/dist/core/contracts/types.js +1 -0
- package/dist/core/errors/product-error.js +74 -0
- package/dist/core/index.js +24 -0
- package/dist/core/safety/policy.js +215 -0
- package/dist/core/safety/raw-api.js +19 -0
- package/dist/core/self-check/live-read-only-probes.js +25 -0
- package/dist/core/self-check/live-trading-verification-probes.js +252 -0
- package/dist/core/self-check/run-self-check.js +91 -0
- package/dist/core/trading/preview.js +330 -0
- package/dist/core/trading/trading-verification.js +137 -0
- package/dist/core/update/check-update.js +295 -0
- package/dist/core/version.js +1 -0
- package/dist/mcp/audit.js +10 -0
- package/dist/mcp/server.js +73 -0
- package/dist/mcp/tool-registry.js +31 -0
- package/dist/mcp/tool-runner.js +144 -0
- package/package.json +48 -0
- package/packages/distribution/docs/cli-only-agent.md +12 -0
- package/packages/distribution/docs/cli.md +49 -0
- package/packages/distribution/docs/index.md +58 -0
- package/packages/distribution/docs/mcp.md +36 -0
- package/packages/distribution/docs/quickstart.md +129 -0
- package/packages/distribution/docs/self-check.md +7 -0
- package/packages/distribution/docs/skills.md +140 -0
- package/packages/distribution/docs/tools.md +35 -0
- package/packages/distribution/docs/trading-verification.md +17 -0
- package/packages/distribution/docs/troubleshooting/index.md +27 -0
- package/packages/distribution/manifests/offline-manifest.json +26 -0
- package/packages/distribution/registry/rapidx.mcp.json +26 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { RAPIDX_SKILLS_SCHEMA_VERSION, RAPIDX_SKILLS_VERSION } from "../contracts/compatibility.js";
|
|
4
|
+
import { SCHEMA_VERSION } from "../contracts/types.js";
|
|
5
|
+
import { RAPIDX_VERSION } from "../version.js";
|
|
6
|
+
export const DEFAULT_RELEASE_MANIFEST_URL = "https://raw.githubusercontent.com/LiquidityTech/ltp-rapidx-skill/main/releases/stable.json";
|
|
7
|
+
export const DEFAULT_UPDATE_CACHE_TTL_SECONDS = 86_400;
|
|
8
|
+
export const BUILTIN_MINIMUM_SUPPORTED_VERSION = "1.0.20";
|
|
9
|
+
export const BUILTIN_MINIMUM_WRITE_VERSION = "1.0.24";
|
|
10
|
+
export async function checkForUpdate(input = {}, env = process.env, cwd = process.cwd()) {
|
|
11
|
+
const manifestUrl = resolveManifestUrl(input, env);
|
|
12
|
+
const cacheTtlSeconds = resolveCacheTtl(input);
|
|
13
|
+
const cacheFile = resolveUpdateCacheFile(env, cwd);
|
|
14
|
+
const cached = loadCachedUpdateState(cacheFile);
|
|
15
|
+
if (!input.force && cached && !isCacheExpired(cached, cacheTtlSeconds)) {
|
|
16
|
+
return {
|
|
17
|
+
...cached.result,
|
|
18
|
+
manifestSource: "cache",
|
|
19
|
+
cacheTtlSeconds
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const manifest = await fetchReleaseManifest(manifestUrl);
|
|
24
|
+
const result = buildUpdateCheckResult(manifest, {
|
|
25
|
+
checkedAt: new Date().toISOString(),
|
|
26
|
+
cacheTtlSeconds,
|
|
27
|
+
manifestUrl,
|
|
28
|
+
manifestSource: "remote"
|
|
29
|
+
});
|
|
30
|
+
saveCachedUpdateState(cacheFile, {
|
|
31
|
+
checkedAt: result.checkedAt,
|
|
32
|
+
cacheTtlSeconds,
|
|
33
|
+
result
|
|
34
|
+
});
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (cached) {
|
|
39
|
+
return {
|
|
40
|
+
...cached.result,
|
|
41
|
+
manifestSource: "cache",
|
|
42
|
+
cacheTtlSeconds,
|
|
43
|
+
error: error instanceof Error ? error.message : String(error)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return buildUnknownUpdateCheckResult({
|
|
47
|
+
checkedAt: new Date().toISOString(),
|
|
48
|
+
cacheTtlSeconds,
|
|
49
|
+
manifestUrl,
|
|
50
|
+
manifestSource: "fallback",
|
|
51
|
+
error: error instanceof Error ? error.message : String(error)
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function buildUpdateCheckResult(manifest, metadata) {
|
|
56
|
+
const minimumSupportedVersion = manifest.minimumSupportedVersion || BUILTIN_MINIMUM_SUPPORTED_VERSION;
|
|
57
|
+
const minimumWriteVersion = maxVersion(manifest.minimumWriteVersion || BUILTIN_MINIMUM_WRITE_VERSION, BUILTIN_MINIMUM_WRITE_VERSION);
|
|
58
|
+
const status = computeUpdateStatus({
|
|
59
|
+
currentVersion: RAPIDX_VERSION,
|
|
60
|
+
latestVersion: manifest.latestCliVersion,
|
|
61
|
+
minimumSupportedVersion,
|
|
62
|
+
minimumWriteVersion
|
|
63
|
+
});
|
|
64
|
+
const writeAllowed = status !== "WRITE_BLOCKED";
|
|
65
|
+
return {
|
|
66
|
+
currentVersion: RAPIDX_VERSION,
|
|
67
|
+
latestVersion: manifest.latestCliVersion,
|
|
68
|
+
minimumSupportedVersion,
|
|
69
|
+
minimumWriteVersion,
|
|
70
|
+
latestMcpSchemaVersion: manifest.latestMcpSchemaVersion,
|
|
71
|
+
currentMcpSchemaVersion: SCHEMA_VERSION,
|
|
72
|
+
skillsVersion: manifest.skillsVersion,
|
|
73
|
+
currentSkillsVersion: RAPIDX_SKILLS_VERSION,
|
|
74
|
+
skillsSchemaVersion: manifest.skillsSchemaVersion,
|
|
75
|
+
currentSkillsSchemaVersion: RAPIDX_SKILLS_SCHEMA_VERSION,
|
|
76
|
+
updateAvailable: compareVersions(RAPIDX_VERSION, manifest.latestCliVersion) < 0,
|
|
77
|
+
writeAllowed,
|
|
78
|
+
status,
|
|
79
|
+
severity: manifest.severity,
|
|
80
|
+
breaking: manifest.breaking,
|
|
81
|
+
checkedAt: metadata.checkedAt,
|
|
82
|
+
cacheTtlSeconds: metadata.cacheTtlSeconds,
|
|
83
|
+
manifestUrl: metadata.manifestUrl,
|
|
84
|
+
manifestSource: metadata.manifestSource,
|
|
85
|
+
releaseNotesUrl: manifest.releaseNotesUrl,
|
|
86
|
+
skillsUpdateRecommended: manifest.skillsUpdateRecommended || compareVersions(RAPIDX_SKILLS_VERSION, manifest.skillsVersion) < 0,
|
|
87
|
+
upgrade: manifest.upgrade,
|
|
88
|
+
...(metadata.error ? { error: metadata.error } : {})
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
export function resolveUpdateCacheFile(env = process.env, cwd = process.cwd()) {
|
|
92
|
+
const stateDir = env.RAPIDX_STATE_DIR ? resolve(env.RAPIDX_STATE_DIR) : resolve(cwd, ".rapidx");
|
|
93
|
+
return join(stateDir, "update-state.json");
|
|
94
|
+
}
|
|
95
|
+
function resolveManifestUrl(input, env) {
|
|
96
|
+
const url = firstNonEmpty(input.manifestUrl, env.RAPIDX_RELEASE_MANIFEST_URL, DEFAULT_RELEASE_MANIFEST_URL);
|
|
97
|
+
assertHttpsUrl(url);
|
|
98
|
+
return url;
|
|
99
|
+
}
|
|
100
|
+
function resolveCacheTtl(input) {
|
|
101
|
+
if (typeof input.maxCacheAgeSeconds === "number" && Number.isFinite(input.maxCacheAgeSeconds) && input.maxCacheAgeSeconds >= 0) {
|
|
102
|
+
return input.maxCacheAgeSeconds;
|
|
103
|
+
}
|
|
104
|
+
return DEFAULT_UPDATE_CACHE_TTL_SECONDS;
|
|
105
|
+
}
|
|
106
|
+
async function fetchReleaseManifest(url) {
|
|
107
|
+
const response = await fetch(url);
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
throw new Error(`Release manifest request failed with HTTP ${response.status}.`);
|
|
110
|
+
}
|
|
111
|
+
const raw = JSON.parse(await response.text());
|
|
112
|
+
return normalizeReleaseManifest(raw);
|
|
113
|
+
}
|
|
114
|
+
function normalizeReleaseManifest(raw) {
|
|
115
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
116
|
+
throw new Error("Release manifest must be a JSON object.");
|
|
117
|
+
}
|
|
118
|
+
const value = raw;
|
|
119
|
+
const upgrade = isPlainObject(value.upgrade) ? value.upgrade : {};
|
|
120
|
+
const manifest = {
|
|
121
|
+
product: readLiteral(value.product, "rapidx", "product"),
|
|
122
|
+
channel: readLiteral(value.channel, "stable", "channel"),
|
|
123
|
+
latestCliVersion: readRequiredString(value.latestCliVersion, "latestCliVersion"),
|
|
124
|
+
minimumSupportedVersion: readOptionalString(value.minimumSupportedVersion, BUILTIN_MINIMUM_SUPPORTED_VERSION),
|
|
125
|
+
minimumWriteVersion: readOptionalString(value.minimumWriteVersion, BUILTIN_MINIMUM_WRITE_VERSION),
|
|
126
|
+
latestMcpSchemaVersion: readOptionalString(value.latestMcpSchemaVersion, SCHEMA_VERSION),
|
|
127
|
+
skillsVersion: readOptionalString(value.skillsVersion, RAPIDX_SKILLS_VERSION),
|
|
128
|
+
skillsSchemaVersion: readOptionalString(value.skillsSchemaVersion, RAPIDX_SKILLS_SCHEMA_VERSION),
|
|
129
|
+
skillsUpdateRecommended: value.skillsUpdateRecommended === true,
|
|
130
|
+
severity: readSeverity(value.severity),
|
|
131
|
+
breaking: value.breaking === true,
|
|
132
|
+
releaseNotesUrl: readOptionalString(value.releaseNotesUrl, ""),
|
|
133
|
+
upgrade: {
|
|
134
|
+
global: readOptionalString(upgrade.global, undefined),
|
|
135
|
+
workspace: readOptionalString(upgrade.workspace, undefined),
|
|
136
|
+
skills: readOptionalString(upgrade.skills, undefined)
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
assertSemver(manifest.latestCliVersion, "latestCliVersion");
|
|
140
|
+
assertSemver(manifest.minimumSupportedVersion, "minimumSupportedVersion");
|
|
141
|
+
assertSemver(manifest.minimumWriteVersion, "minimumWriteVersion");
|
|
142
|
+
return manifest;
|
|
143
|
+
}
|
|
144
|
+
function fallbackManifest() {
|
|
145
|
+
return {
|
|
146
|
+
product: "rapidx",
|
|
147
|
+
channel: "stable",
|
|
148
|
+
latestCliVersion: RAPIDX_VERSION,
|
|
149
|
+
minimumSupportedVersion: BUILTIN_MINIMUM_SUPPORTED_VERSION,
|
|
150
|
+
minimumWriteVersion: BUILTIN_MINIMUM_WRITE_VERSION,
|
|
151
|
+
latestMcpSchemaVersion: SCHEMA_VERSION,
|
|
152
|
+
skillsVersion: RAPIDX_SKILLS_VERSION,
|
|
153
|
+
skillsSchemaVersion: RAPIDX_SKILLS_SCHEMA_VERSION,
|
|
154
|
+
skillsUpdateRecommended: false,
|
|
155
|
+
severity: "none",
|
|
156
|
+
breaking: false,
|
|
157
|
+
releaseNotesUrl: "",
|
|
158
|
+
upgrade: {}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function buildUnknownUpdateCheckResult(metadata) {
|
|
162
|
+
const manifest = fallbackManifest();
|
|
163
|
+
return {
|
|
164
|
+
currentVersion: RAPIDX_VERSION,
|
|
165
|
+
latestVersion: RAPIDX_VERSION,
|
|
166
|
+
minimumSupportedVersion: manifest.minimumSupportedVersion,
|
|
167
|
+
minimumWriteVersion: manifest.minimumWriteVersion,
|
|
168
|
+
latestMcpSchemaVersion: manifest.latestMcpSchemaVersion,
|
|
169
|
+
currentMcpSchemaVersion: SCHEMA_VERSION,
|
|
170
|
+
skillsVersion: manifest.skillsVersion,
|
|
171
|
+
currentSkillsVersion: RAPIDX_SKILLS_VERSION,
|
|
172
|
+
skillsSchemaVersion: manifest.skillsSchemaVersion,
|
|
173
|
+
currentSkillsSchemaVersion: RAPIDX_SKILLS_SCHEMA_VERSION,
|
|
174
|
+
updateAvailable: false,
|
|
175
|
+
writeAllowed: true,
|
|
176
|
+
status: "UNKNOWN",
|
|
177
|
+
severity: "none",
|
|
178
|
+
breaking: false,
|
|
179
|
+
checkedAt: metadata.checkedAt,
|
|
180
|
+
cacheTtlSeconds: metadata.cacheTtlSeconds,
|
|
181
|
+
manifestUrl: metadata.manifestUrl,
|
|
182
|
+
manifestSource: metadata.manifestSource,
|
|
183
|
+
releaseNotesUrl: "",
|
|
184
|
+
skillsUpdateRecommended: false,
|
|
185
|
+
upgrade: {},
|
|
186
|
+
error: metadata.error
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function computeUpdateStatus(input) {
|
|
190
|
+
if (compareVersions(input.currentVersion, input.minimumWriteVersion) < 0) {
|
|
191
|
+
return "WRITE_BLOCKED";
|
|
192
|
+
}
|
|
193
|
+
if (compareVersions(input.currentVersion, input.minimumSupportedVersion) < 0) {
|
|
194
|
+
return "UPGRADE_REQUIRED";
|
|
195
|
+
}
|
|
196
|
+
if (compareVersions(input.currentVersion, input.latestVersion) < 0) {
|
|
197
|
+
return "UPDATE_AVAILABLE";
|
|
198
|
+
}
|
|
199
|
+
return "CURRENT";
|
|
200
|
+
}
|
|
201
|
+
function compareVersions(left, right) {
|
|
202
|
+
const a = parseVersion(left);
|
|
203
|
+
const b = parseVersion(right);
|
|
204
|
+
for (const index of [0, 1, 2]) {
|
|
205
|
+
const leftPart = a[index];
|
|
206
|
+
const rightPart = b[index];
|
|
207
|
+
if (leftPart !== rightPart) {
|
|
208
|
+
return leftPart < rightPart ? -1 : 1;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return 0;
|
|
212
|
+
}
|
|
213
|
+
function maxVersion(left, right) {
|
|
214
|
+
return compareVersions(left, right) >= 0 ? left : right;
|
|
215
|
+
}
|
|
216
|
+
function parseVersion(version) {
|
|
217
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(version);
|
|
218
|
+
if (!match) {
|
|
219
|
+
throw new Error(`Invalid semver version: ${version}`);
|
|
220
|
+
}
|
|
221
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
222
|
+
}
|
|
223
|
+
function assertSemver(version, field) {
|
|
224
|
+
try {
|
|
225
|
+
parseVersion(version);
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
throw new Error(`Release manifest field ${field} must be semver.`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function loadCachedUpdateState(filePath) {
|
|
232
|
+
if (!existsSync(filePath)) {
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
const parsed = JSON.parse(readFileSync(filePath, "utf8"));
|
|
237
|
+
if (!isPlainObject(parsed) || !isPlainObject(parsed.result) || typeof parsed.checkedAt !== "string") {
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
return parsed;
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function saveCachedUpdateState(filePath, state) {
|
|
247
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
248
|
+
writeFileSync(filePath, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 });
|
|
249
|
+
}
|
|
250
|
+
function isCacheExpired(state, ttlSeconds) {
|
|
251
|
+
if (ttlSeconds === 0) {
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
const checkedAt = Date.parse(state.checkedAt);
|
|
255
|
+
if (!Number.isFinite(checkedAt)) {
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
return Date.now() - checkedAt > ttlSeconds * 1000;
|
|
259
|
+
}
|
|
260
|
+
function readRequiredString(value, field) {
|
|
261
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
262
|
+
throw new Error(`Release manifest field ${field} is required.`);
|
|
263
|
+
}
|
|
264
|
+
return value;
|
|
265
|
+
}
|
|
266
|
+
function readOptionalString(value, fallback) {
|
|
267
|
+
return typeof value === "string" && value.length > 0 ? value : fallback ?? "";
|
|
268
|
+
}
|
|
269
|
+
function readLiteral(value, expected, field) {
|
|
270
|
+
if (value !== expected) {
|
|
271
|
+
throw new Error(`Release manifest field ${field} must be ${expected}.`);
|
|
272
|
+
}
|
|
273
|
+
return expected;
|
|
274
|
+
}
|
|
275
|
+
function readSeverity(value) {
|
|
276
|
+
return value === "recommended" || value === "required" || value === "critical" || value === "none" ? value : "none";
|
|
277
|
+
}
|
|
278
|
+
function firstNonEmpty(...values) {
|
|
279
|
+
return values.find((value) => typeof value === "string" && value.length > 0) ?? DEFAULT_RELEASE_MANIFEST_URL;
|
|
280
|
+
}
|
|
281
|
+
function isPlainObject(value) {
|
|
282
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
283
|
+
}
|
|
284
|
+
function assertHttpsUrl(value) {
|
|
285
|
+
let parsed;
|
|
286
|
+
try {
|
|
287
|
+
parsed = new URL(value);
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
throw new Error("Release manifest URL must be a valid HTTPS URL.");
|
|
291
|
+
}
|
|
292
|
+
if (parsed.protocol !== "https:") {
|
|
293
|
+
throw new Error("Release manifest URL must use HTTPS.");
|
|
294
|
+
}
|
|
295
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const RAPIDX_VERSION = "1.0.26";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createInterface } from "node:readline";
|
|
2
|
+
import { stdin, stdout } from "node:process";
|
|
3
|
+
import { buildCompatibilityReport, RAPIDX_VERSION } from "../core/index.js";
|
|
4
|
+
import { getMcpToolDefinitions } from "./tool-registry.js";
|
|
5
|
+
import { runMcpTool } from "./tool-runner.js";
|
|
6
|
+
export async function handleMcpRequest(request) {
|
|
7
|
+
if (request.method === "initialize") {
|
|
8
|
+
return {
|
|
9
|
+
jsonrpc: "2.0",
|
|
10
|
+
id: request.id,
|
|
11
|
+
result: {
|
|
12
|
+
protocolVersion: "2025-03-26",
|
|
13
|
+
serverInfo: { name: "rapidx", version: RAPIDX_VERSION },
|
|
14
|
+
capabilities: { tools: {} },
|
|
15
|
+
compatibility: buildCompatibilityReport()
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
if (request.method === "tools/list") {
|
|
20
|
+
return {
|
|
21
|
+
jsonrpc: "2.0",
|
|
22
|
+
id: request.id,
|
|
23
|
+
result: {
|
|
24
|
+
tools: getMcpToolDefinitions().map((tool) => ({
|
|
25
|
+
name: tool.name,
|
|
26
|
+
description: tool.description,
|
|
27
|
+
inputSchema: tool.inputSchema
|
|
28
|
+
}))
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (request.method === "tools/call") {
|
|
33
|
+
const params = request.params ?? {};
|
|
34
|
+
const name = typeof params.name === "string" ? params.name : "";
|
|
35
|
+
const args = params.arguments && typeof params.arguments === "object" ? params.arguments : {};
|
|
36
|
+
const result = await runMcpTool(name, args);
|
|
37
|
+
return {
|
|
38
|
+
jsonrpc: "2.0",
|
|
39
|
+
id: request.id,
|
|
40
|
+
result: {
|
|
41
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
42
|
+
isError: !result.ok
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
jsonrpc: "2.0",
|
|
48
|
+
id: request.id,
|
|
49
|
+
error: { code: -32601, message: `Unsupported MCP method: ${request.method}` }
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export async function startStdioServer() {
|
|
53
|
+
const rl = createInterface({ input: stdin });
|
|
54
|
+
for await (const line of rl) {
|
|
55
|
+
if (!line.trim()) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const request = JSON.parse(line);
|
|
60
|
+
const response = await handleMcpRequest(request);
|
|
61
|
+
stdout.write(`${JSON.stringify(response)}\n`);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
stdout.write(`${JSON.stringify({
|
|
65
|
+
jsonrpc: "2.0",
|
|
66
|
+
error: {
|
|
67
|
+
code: -32700,
|
|
68
|
+
message: error instanceof Error ? error.message : String(error)
|
|
69
|
+
}
|
|
70
|
+
})}\n`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { findCapabilityById, findCapabilityByMcpTool, inputSchemaForName, listMcpCapabilities, SCHEMA_VERSION } from "../core/index.js";
|
|
2
|
+
export function getMcpToolDefinitions() {
|
|
3
|
+
const seen = new Set();
|
|
4
|
+
const tools = [];
|
|
5
|
+
for (const capability of listMcpCapabilities()) {
|
|
6
|
+
if (!capability.mcpTool || seen.has(capability.mcpTool)) {
|
|
7
|
+
continue;
|
|
8
|
+
}
|
|
9
|
+
seen.add(capability.mcpTool);
|
|
10
|
+
const canonical = capabilityForTool(capability.mcpTool);
|
|
11
|
+
tools.push({
|
|
12
|
+
name: capability.mcpTool,
|
|
13
|
+
description: `RapidX ${canonical.capabilityId} (${canonical.riskLevel}, schema ${SCHEMA_VERSION})`,
|
|
14
|
+
inputSchema: inputSchemaForName(canonical.inputSchema),
|
|
15
|
+
capability: canonical
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return tools.sort((a, b) => a.name.localeCompare(b.name));
|
|
19
|
+
}
|
|
20
|
+
export function capabilityForTool(toolName) {
|
|
21
|
+
const overrideId = toolName === "rapidx/tools"
|
|
22
|
+
? "schema.discover"
|
|
23
|
+
: toolName === "rapidx/self-check"
|
|
24
|
+
? "self-check.run"
|
|
25
|
+
: undefined;
|
|
26
|
+
const capability = overrideId ? findCapabilityById(overrideId) : findCapabilityByMcpTool(toolName);
|
|
27
|
+
if (!capability) {
|
|
28
|
+
throw new Error(`Unknown MCP tool: ${toolName}`);
|
|
29
|
+
}
|
|
30
|
+
return capability;
|
|
31
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { consumePreview, createTargetedTradePreview, createTradePreview, defaultSafetyPolicy, executeRapidXCapability, findCapabilityById, getSchemaResult, makeEvidence, makePreviewStore, makeSafetyState, normalizeUnknownError, runSelfCheck, runTradingVerification, verifyPreview, buildLiveReadOnlySelfCheck, buildLiveTradingVerificationProbes, checkForUpdate, } from "../core/index.js";
|
|
2
|
+
import { capabilityForTool, getMcpToolDefinitions } from "./tool-registry.js";
|
|
3
|
+
import { writeMcpAudit } from "./audit.js";
|
|
4
|
+
const previewStore = makePreviewStore();
|
|
5
|
+
const safetyState = makeSafetyState();
|
|
6
|
+
export async function runMcpTool(toolName, input = {}) {
|
|
7
|
+
try {
|
|
8
|
+
if (toolName === "rapidx/tools") {
|
|
9
|
+
const data = {
|
|
10
|
+
schemaVersion: getSchemaResult().schemaVersion,
|
|
11
|
+
tools: getMcpToolDefinitions().map((tool) => ({
|
|
12
|
+
name: tool.name,
|
|
13
|
+
riskLevel: tool.capability.riskLevel,
|
|
14
|
+
inputSchema: tool.capability.inputSchema,
|
|
15
|
+
outputSchema: tool.capability.outputSchema,
|
|
16
|
+
previewRequired: tool.capability.previewRequired
|
|
17
|
+
}))
|
|
18
|
+
};
|
|
19
|
+
const auditId = writeMcpAudit("tools/list", "PASS", { toolName });
|
|
20
|
+
return { ok: true, status: "PASS", data, auditId, evidence: [makeEvidence("rapidx/tools")] };
|
|
21
|
+
}
|
|
22
|
+
if (toolName === "rapidx/self-check") {
|
|
23
|
+
const report = await runSelfCheck({
|
|
24
|
+
input: { scope: input.scope === "deep" ? "deep" : "quick", readOnly: true, checkUpdates: input.checkUpdates === true },
|
|
25
|
+
...buildLiveReadOnlySelfCheck(input)
|
|
26
|
+
});
|
|
27
|
+
const auditId = writeMcpAudit("self-check", report.status, { toolName, status: report.status });
|
|
28
|
+
return { ok: report.status === "PASS", status: report.status, data: report, auditId, evidence: report.evidence };
|
|
29
|
+
}
|
|
30
|
+
if (toolName === "rapidx/update/check") {
|
|
31
|
+
const updateInput = { force: input.force === true };
|
|
32
|
+
if (typeof input.manifestUrl === "string") {
|
|
33
|
+
updateInput.manifestUrl = input.manifestUrl;
|
|
34
|
+
}
|
|
35
|
+
if (typeof input.maxCacheAgeSeconds === "number") {
|
|
36
|
+
updateInput.maxCacheAgeSeconds = input.maxCacheAgeSeconds;
|
|
37
|
+
}
|
|
38
|
+
const update = await checkForUpdate(updateInput);
|
|
39
|
+
const status = envelopeStatusForUpdate(update.status);
|
|
40
|
+
const auditId = writeMcpAudit("update-check", status, { toolName, updateStatus: update.status });
|
|
41
|
+
return { ok: status === "PASS", status, data: update, auditId, evidence: [makeEvidence(toolName)] };
|
|
42
|
+
}
|
|
43
|
+
if (toolName === "rapidx/trading-verification" || toolName === "rapidx/trade/verify-live") {
|
|
44
|
+
const report = await runTradingVerification(input, buildLiveTradingVerificationProbes(input));
|
|
45
|
+
const auditId = writeMcpAudit("trading-verification", report.status, { toolName, status: report.status });
|
|
46
|
+
return tradingVerificationEnvelope(toolName, report, auditId);
|
|
47
|
+
}
|
|
48
|
+
const capability = capabilityForTool(toolName);
|
|
49
|
+
if (toolName === "rapidx/order/preview" || toolName === "rapidx/order/place-preview") {
|
|
50
|
+
const previewCapability = findCapabilityById("order.preview");
|
|
51
|
+
if (!previewCapability) {
|
|
52
|
+
throw new Error("order.preview capability missing");
|
|
53
|
+
}
|
|
54
|
+
const preview = createTradePreview(previewCapability, input, { ...defaultSafetyPolicy(), tradingEnabled: true, readOnly: false }, safetyState, previewStore);
|
|
55
|
+
const auditId = writeMcpAudit("trade-preview", "PASS", { toolName, capabilityId: capability.capabilityId, previewId: preview.previewId });
|
|
56
|
+
return { ok: true, status: "PASS", data: preview, auditId, evidence: [makeEvidence(toolName)] };
|
|
57
|
+
}
|
|
58
|
+
const concretePreviewTarget = concretePreviewTargetForTool(toolName);
|
|
59
|
+
if (concretePreviewTarget) {
|
|
60
|
+
const targetCapability = findCapabilityById(concretePreviewTarget);
|
|
61
|
+
if (!targetCapability) {
|
|
62
|
+
throw new Error(`${concretePreviewTarget} capability missing`);
|
|
63
|
+
}
|
|
64
|
+
const preview = createTargetedTradePreview(targetCapability, input, { ...defaultSafetyPolicy(), tradingEnabled: true, readOnly: false }, safetyState, previewStore);
|
|
65
|
+
const auditId = writeMcpAudit("trade-preview", "PASS", { toolName, capabilityId: targetCapability.capabilityId, previewId: preview.previewId });
|
|
66
|
+
return { ok: true, status: "PASS", data: preview, auditId, evidence: [makeEvidence(toolName)] };
|
|
67
|
+
}
|
|
68
|
+
if (toolName === "rapidx/trade/preview") {
|
|
69
|
+
const targetCapabilityId = typeof input.targetCapabilityId === "string" ? input.targetCapabilityId : "";
|
|
70
|
+
const targetCapability = targetCapabilityId ? findCapabilityById(targetCapabilityId) : undefined;
|
|
71
|
+
if (!targetCapability) {
|
|
72
|
+
throw new Error(`Unknown trade preview target: ${targetCapabilityId || "<missing>"}`);
|
|
73
|
+
}
|
|
74
|
+
if (targetCapability.operationType !== "TRADE_WRITE" || isPreviewCapability(targetCapability.capabilityId)) {
|
|
75
|
+
throw new Error("trade preview target must be a non-preview trade write capability.");
|
|
76
|
+
}
|
|
77
|
+
const preview = createTargetedTradePreview(targetCapability, input, { ...defaultSafetyPolicy(), tradingEnabled: true, readOnly: false }, safetyState, previewStore);
|
|
78
|
+
const auditId = writeMcpAudit("trade-preview", "PASS", { toolName, capabilityId: targetCapability.capabilityId, previewId: preview.previewId });
|
|
79
|
+
return { ok: true, status: "PASS", data: preview, auditId, evidence: [makeEvidence(toolName)] };
|
|
80
|
+
}
|
|
81
|
+
if (capability.previewRequired) {
|
|
82
|
+
verifyPreview(previewStore, typeof input.previewId === "string" ? input.previewId : undefined, capability, input);
|
|
83
|
+
const consentId = input.continueConsentId;
|
|
84
|
+
if (typeof consentId !== "string" || consentId.length === 0) {
|
|
85
|
+
return { ok: false, status: "BLOCKED", code: "RMCP20001", message: "continueConsentId is required for trade writes.", evidence: [makeEvidence(toolName)] };
|
|
86
|
+
}
|
|
87
|
+
consumePreview(previewStore, typeof input.previewId === "string" ? input.previewId : undefined, capability, input);
|
|
88
|
+
}
|
|
89
|
+
const data = await executeRapidXCapability(capability.capabilityId, input);
|
|
90
|
+
const auditId = writeMcpAudit("tool-call", "PASS", { toolName, capabilityId: capability.capabilityId });
|
|
91
|
+
return {
|
|
92
|
+
ok: true,
|
|
93
|
+
status: "PASS",
|
|
94
|
+
data,
|
|
95
|
+
auditId,
|
|
96
|
+
evidence: [makeEvidence(toolName, "real_tool_call")]
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
const productError = normalizeUnknownError(error, "RMCP12003");
|
|
101
|
+
return {
|
|
102
|
+
ok: false,
|
|
103
|
+
status: productError.status,
|
|
104
|
+
code: productError.code.replace(/^RCORE/, "RMCP"),
|
|
105
|
+
message: productError.message,
|
|
106
|
+
evidence: [makeEvidence(toolName)]
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function concretePreviewTargetForTool(toolName) {
|
|
111
|
+
if (toolName === "rapidx/order/amend-preview") {
|
|
112
|
+
return "order.amend";
|
|
113
|
+
}
|
|
114
|
+
if (toolName === "rapidx/order/cancel-preview") {
|
|
115
|
+
return "order.cancel";
|
|
116
|
+
}
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
function tradingVerificationEnvelope(toolName, report, auditId) {
|
|
120
|
+
if (report.status === "PASS") {
|
|
121
|
+
return { ok: true, status: "PASS", data: report, auditId, evidence: [makeEvidence(toolName)] };
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
ok: false,
|
|
125
|
+
status: report.status,
|
|
126
|
+
code: (report.errorCode ?? "RCORE10002").replace(/^RCORE/, "RMCP"),
|
|
127
|
+
message: report.errorMessage ?? `Trading verification failed: ${report.errorCode ?? report.status}.`,
|
|
128
|
+
data: report,
|
|
129
|
+
auditId,
|
|
130
|
+
evidence: [makeEvidence(toolName)]
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function isPreviewCapability(capabilityId) {
|
|
134
|
+
return capabilityId.endsWith(".preview") || capabilityId.endsWith("-preview");
|
|
135
|
+
}
|
|
136
|
+
function envelopeStatusForUpdate(status) {
|
|
137
|
+
if (status === "WRITE_BLOCKED" || status === "UPGRADE_REQUIRED") {
|
|
138
|
+
return "BLOCKED";
|
|
139
|
+
}
|
|
140
|
+
if (status === "UNKNOWN") {
|
|
141
|
+
return "NOT_VERIFIED";
|
|
142
|
+
}
|
|
143
|
+
return "PASS";
|
|
144
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@liquiditytech/rapidx-cli",
|
|
3
|
+
"version": "1.0.26",
|
|
4
|
+
"description": "RapidX CLI, MCP server mode, registry, and release assets.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": false,
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+ssh://git@github.com/LiquidityTech/ltp-rapidx-cli-mcp.git"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"registry": "https://registry.npmjs.org/",
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"rapidx": "dist/cli/bin.js"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist/cli/**/*.js",
|
|
20
|
+
"dist/core/**/*.js",
|
|
21
|
+
"dist/mcp/**/*.js",
|
|
22
|
+
"packages/distribution/docs",
|
|
23
|
+
"packages/distribution/registry",
|
|
24
|
+
"packages/distribution/manifests",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"prebuild": "node packages/distribution/scripts/clean-dist.mjs",
|
|
29
|
+
"build": "tsc -p tsconfig.json && node packages/distribution/scripts/normalize-dist.mjs",
|
|
30
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"test:release-smoke": "vitest run packages/distribution/tests/release-smoke.test.ts",
|
|
33
|
+
"validate:release": "node dist/distribution/src/validate-release.js"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=20"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@modelcontextprotocol/sdk": "1.29.0",
|
|
40
|
+
"zod": "3.25.76"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^22.0.0",
|
|
44
|
+
"tsx": "^4.19.0",
|
|
45
|
+
"typescript": "^5.8.0",
|
|
46
|
+
"vitest": "^4.1.1"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# CLI-only Agents
|
|
2
|
+
|
|
3
|
+
Some agent hosts cannot call MCP tools. They can still call the CLI directly.
|
|
4
|
+
|
|
5
|
+
Use direct commands only:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
rapidx schema --json
|
|
9
|
+
rapidx self-check --read-only --json
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
When passing trade input, use a JSON file or stdin JSON. Avoid interpreter wrappers and bridge scripts.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# CLI
|
|
2
|
+
|
|
3
|
+
The `rapidx` CLI is the atomic interface for agents that cannot use MCP.
|
|
4
|
+
|
|
5
|
+
Official environment variables:
|
|
6
|
+
|
|
7
|
+
- `LTP_ACCESS_KEY`
|
|
8
|
+
- `LTP_SECRET_KEY`
|
|
9
|
+
- `LTP_API_HOST`
|
|
10
|
+
|
|
11
|
+
`LTP_API_HOST` is required. Use the API host provided for the current environment.
|
|
12
|
+
|
|
13
|
+
Supported conventions:
|
|
14
|
+
|
|
15
|
+
- Use `--json` for machine output.
|
|
16
|
+
- Use `--input @/absolute/path/input.json` for structured input.
|
|
17
|
+
- Use stdin JSON when the host supports it.
|
|
18
|
+
- Do not create bridge scripts.
|
|
19
|
+
- Use `rapidx order place-preview` for `order.place`.
|
|
20
|
+
- Use `rapidx order amend-preview` for `order.amend`.
|
|
21
|
+
- Use `rapidx order cancel-preview` for `order.cancel`.
|
|
22
|
+
- Use `rapidx trade preview` with `targetCapabilityId` for non-order trade writes, including position, account, and algo writes.
|
|
23
|
+
- `rapidx trade preview` input is flat JSON; do not wrap target parameters under `params`.
|
|
24
|
+
- Treat `maxNotional` as a safety upper bound, not as the target order amount. Check symbol `minNotional` before increasing a requested amount.
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
rapidx schema --json
|
|
30
|
+
rapidx update check --json
|
|
31
|
+
rapidx self-check --read-only --json
|
|
32
|
+
rapidx self-check --read-only --check-updates --json
|
|
33
|
+
rapidx account balance --input '{"mode":"portfolio"}' --json
|
|
34
|
+
rapidx account balance --input '{"mode":"account"}' --json
|
|
35
|
+
rapidx order place-preview --input @/absolute/path/order-preview.json --json
|
|
36
|
+
rapidx order amend-preview --input @/absolute/path/order-amend-preview.json --json
|
|
37
|
+
rapidx order cancel-preview --input @/absolute/path/order-cancel-preview.json --json
|
|
38
|
+
rapidx trade preview --input @/absolute/path/trade-preview.json --json
|
|
39
|
+
rapidx position history --input @/absolute/path/position-history.json --json
|
|
40
|
+
rapidx account set-position-mode --input @/absolute/path/set-position-mode.json --json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`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
|
+
|
|
45
|
+
`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
|
+
|
|
47
|
+
`rapidx account set-position-mode` is a trade write. It requires a matching preview token and a `continueConsentId` before execution.
|
|
48
|
+
|
|
49
|
+
`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.
|