@okx_ai/okx-trade-mcp 1.3.2-beta.2 → 1.3.2-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +284 -106
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.js +78 -1
- package/scripts/postinstall-download.js +0 -152
package/dist/index.js
CHANGED
|
@@ -22,18 +22,21 @@ import {
|
|
|
22
22
|
import { homedir as homedir2 } from "os";
|
|
23
23
|
import { join as join2, dirname } from "path";
|
|
24
24
|
import { createHmac } from "crypto";
|
|
25
|
+
import { spawn, execFile as execFile2 } from "child_process";
|
|
26
|
+
import { homedir as homedir3 } from "os";
|
|
27
|
+
import { join as join3 } from "path";
|
|
25
28
|
import fs from "fs";
|
|
26
29
|
import path from "path";
|
|
27
30
|
import os from "os";
|
|
28
31
|
import { writeFileSync as writeFileSync2, renameSync as renameSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
29
|
-
import { join as
|
|
32
|
+
import { join as join4, resolve, basename, sep } from "path";
|
|
30
33
|
import { randomUUID } from "crypto";
|
|
31
34
|
import yauzl from "yauzl";
|
|
32
|
-
import { join as
|
|
33
|
-
import { homedir as homedir3 } from "os";
|
|
34
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync3 } from "fs";
|
|
35
|
-
import { join as join6, dirname as dirname4 } from "path";
|
|
35
|
+
import { join as join6, dirname as dirname3 } from "path";
|
|
36
36
|
import { homedir as homedir4 } from "os";
|
|
37
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync3 } from "fs";
|
|
38
|
+
import { join as join7, dirname as dirname4 } from "path";
|
|
39
|
+
import { homedir as homedir5 } from "os";
|
|
37
40
|
|
|
38
41
|
// ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/error.js
|
|
39
42
|
function getLineColFromPtr(string, ptr) {
|
|
@@ -722,8 +725,8 @@ function parse(toml, { maxDepth = 1e3, integersAsBigInt } = {}) {
|
|
|
722
725
|
|
|
723
726
|
// ../core/dist/index.js
|
|
724
727
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync4 } from "fs";
|
|
725
|
-
import { join as
|
|
726
|
-
import { homedir as
|
|
728
|
+
import { join as join8 } from "path";
|
|
729
|
+
import { homedir as homedir6 } from "os";
|
|
727
730
|
import fs2 from "fs";
|
|
728
731
|
import path2 from "path";
|
|
729
732
|
import os2 from "os";
|
|
@@ -731,6 +734,8 @@ import * as fs3 from "fs";
|
|
|
731
734
|
import * as path3 from "path";
|
|
732
735
|
import * as os3 from "os";
|
|
733
736
|
import { execFileSync } from "child_process";
|
|
737
|
+
import { join as join12 } from "path";
|
|
738
|
+
import { homedir as homedir10 } from "os";
|
|
734
739
|
var EXEC_TIMEOUT_MS = 3e4;
|
|
735
740
|
var ALLOWED_DOMAIN_RE = /^[\w.-]+\.okx\.com$/;
|
|
736
741
|
var PILOT_BIN_DIR = join(homedir(), ".okx", "bin");
|
|
@@ -1042,6 +1047,11 @@ var ConfigError = class extends OkxMcpError {
|
|
|
1042
1047
|
super("ConfigError", message, { suggestion });
|
|
1043
1048
|
}
|
|
1044
1049
|
};
|
|
1050
|
+
var NotLoggedInError = class extends ConfigError {
|
|
1051
|
+
constructor(suggestion = "Run `okx auth login` to authenticate.") {
|
|
1052
|
+
super("Not logged in.", suggestion);
|
|
1053
|
+
}
|
|
1054
|
+
};
|
|
1045
1055
|
var ValidationError = class extends OkxMcpError {
|
|
1046
1056
|
constructor(message, suggestion) {
|
|
1047
1057
|
super("ValidationError", message, { suggestion });
|
|
@@ -1094,6 +1104,97 @@ function toToolErrorPayload(error, fallbackEndpoint) {
|
|
|
1094
1104
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1095
1105
|
};
|
|
1096
1106
|
}
|
|
1107
|
+
var EXIT_CODES = {
|
|
1108
|
+
SUCCESS: 0,
|
|
1109
|
+
UNAUTHORIZED_CALLER: 1,
|
|
1110
|
+
NOT_LOGGED_IN: 2,
|
|
1111
|
+
REFRESH_FAILED: 3
|
|
1112
|
+
};
|
|
1113
|
+
var EXEC_TIMEOUT_MS2 = 5e3;
|
|
1114
|
+
var AUTH_BIN_DIR = join3(homedir3(), ".okx", "bin");
|
|
1115
|
+
function getAuthBinaryPath() {
|
|
1116
|
+
if (process.env.OKX_AUTH_BIN) {
|
|
1117
|
+
return process.env.OKX_AUTH_BIN;
|
|
1118
|
+
}
|
|
1119
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
1120
|
+
return join3(AUTH_BIN_DIR, `okx-auth${ext}`);
|
|
1121
|
+
}
|
|
1122
|
+
function execAuthToken() {
|
|
1123
|
+
const binPath = getAuthBinaryPath();
|
|
1124
|
+
return new Promise((resolve3, reject) => {
|
|
1125
|
+
const child = spawn(binPath, ["token"], {
|
|
1126
|
+
stdio: ["ignore", "ignore", "inherit", "pipe"]
|
|
1127
|
+
// stdin stdout stderr fd3 (pipe)
|
|
1128
|
+
});
|
|
1129
|
+
const chunks = [];
|
|
1130
|
+
const fd3 = child.stdio[3];
|
|
1131
|
+
fd3.on("data", (chunk) => chunks.push(chunk));
|
|
1132
|
+
child.on("error", (err) => {
|
|
1133
|
+
reject(new ConfigError(
|
|
1134
|
+
`Failed to spawn okx-auth: ${err.message}`,
|
|
1135
|
+
"Ensure the okx-auth binary exists and is executable."
|
|
1136
|
+
));
|
|
1137
|
+
});
|
|
1138
|
+
child.on("close", (code) => {
|
|
1139
|
+
if (code === EXIT_CODES.SUCCESS) {
|
|
1140
|
+
const token = Buffer.concat(chunks).toString("utf-8").trim();
|
|
1141
|
+
if (!token) {
|
|
1142
|
+
reject(new AuthenticationError(
|
|
1143
|
+
"okx-auth returned empty token.",
|
|
1144
|
+
"Run `okx auth login` to re-authenticate."
|
|
1145
|
+
));
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
resolve3(token);
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
if (code === EXIT_CODES.NOT_LOGGED_IN) {
|
|
1152
|
+
reject(new NotLoggedInError());
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
if (code === EXIT_CODES.UNAUTHORIZED_CALLER) {
|
|
1156
|
+
reject(new AuthenticationError(
|
|
1157
|
+
"okx-auth rejected the caller (unauthorized).",
|
|
1158
|
+
"Ensure you are running from a trusted OKX tool."
|
|
1159
|
+
));
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
if (code === EXIT_CODES.REFRESH_FAILED) {
|
|
1163
|
+
reject(new AuthenticationError(
|
|
1164
|
+
"Token refresh failed.",
|
|
1165
|
+
"Run `okx auth login` to re-authenticate."
|
|
1166
|
+
));
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
reject(new AuthenticationError(
|
|
1170
|
+
`okx-auth token exited with code ${code}.`,
|
|
1171
|
+
"Run `okx auth login` to re-authenticate."
|
|
1172
|
+
));
|
|
1173
|
+
});
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
function execAuthStatus() {
|
|
1177
|
+
const binPath = getAuthBinaryPath();
|
|
1178
|
+
return new Promise((resolve3) => {
|
|
1179
|
+
execFile2(
|
|
1180
|
+
binPath,
|
|
1181
|
+
["status", "--json"],
|
|
1182
|
+
{ timeout: EXEC_TIMEOUT_MS2, encoding: "utf-8" },
|
|
1183
|
+
(error, stdout) => {
|
|
1184
|
+
if (error) {
|
|
1185
|
+
resolve3(null);
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
try {
|
|
1189
|
+
const result = JSON.parse(stdout);
|
|
1190
|
+
resolve3(result);
|
|
1191
|
+
} catch {
|
|
1192
|
+
resolve3(null);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
);
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1097
1198
|
function sleep(ms) {
|
|
1098
1199
|
return new Promise((resolve3) => {
|
|
1099
1200
|
setTimeout(resolve3, ms);
|
|
@@ -1225,6 +1326,7 @@ function maskKey(key) {
|
|
|
1225
1326
|
if (key.length <= 8) return "***";
|
|
1226
1327
|
return `${key.slice(0, 3)}***${key.slice(-3)}`;
|
|
1227
1328
|
}
|
|
1329
|
+
var TOKEN_CACHE_TTL_MS = 6e4;
|
|
1228
1330
|
function vlog2(message) {
|
|
1229
1331
|
process.stderr.write(`[verbose] ${message}
|
|
1230
1332
|
`);
|
|
@@ -1233,6 +1335,8 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1233
1335
|
config;
|
|
1234
1336
|
rateLimiter;
|
|
1235
1337
|
dispatcher;
|
|
1338
|
+
cachedAccessToken;
|
|
1339
|
+
cachedAccessTokenAt = 0;
|
|
1236
1340
|
pilot;
|
|
1237
1341
|
constructor(config) {
|
|
1238
1342
|
this.config = config;
|
|
@@ -1247,6 +1351,51 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1247
1351
|
hasCustomProxy: !!config.proxyUrl
|
|
1248
1352
|
});
|
|
1249
1353
|
}
|
|
1354
|
+
/**
|
|
1355
|
+
* Resolve OAuth access token via the okx-auth binary (fd3 pipe).
|
|
1356
|
+
* Caches the token for 60 s to avoid spawning the binary on every
|
|
1357
|
+
* request. The binary handles refresh internally (300s TTL lead),
|
|
1358
|
+
* so periodic re-calls let it serve a fresh token when needed.
|
|
1359
|
+
* Returns null when not logged in.
|
|
1360
|
+
*/
|
|
1361
|
+
async resolveAccessToken() {
|
|
1362
|
+
if (this.cachedAccessToken && Date.now() - this.cachedAccessTokenAt < TOKEN_CACHE_TTL_MS) {
|
|
1363
|
+
return this.cachedAccessToken;
|
|
1364
|
+
}
|
|
1365
|
+
try {
|
|
1366
|
+
const token = await execAuthToken();
|
|
1367
|
+
this.cachedAccessToken = token;
|
|
1368
|
+
this.cachedAccessTokenAt = Date.now();
|
|
1369
|
+
return token;
|
|
1370
|
+
} catch (e) {
|
|
1371
|
+
if (e instanceof NotLoggedInError) {
|
|
1372
|
+
return null;
|
|
1373
|
+
}
|
|
1374
|
+
throw e;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Dynamic auth — determines auth method per request.
|
|
1379
|
+
*
|
|
1380
|
+
* 1. API key in config → HMAC signing (no OAuth fallback)
|
|
1381
|
+
* 2. OAuth token via okx-auth binary → Bearer token
|
|
1382
|
+
* 3. Neither → throw ConfigError
|
|
1383
|
+
*/
|
|
1384
|
+
async applyAuth(headers, method, requestPath, bodyJson, timestamp) {
|
|
1385
|
+
if (this.config.apiKey && this.config.secretKey && this.config.passphrase) {
|
|
1386
|
+
this.setAuthHeaders(headers, method, requestPath, bodyJson, timestamp);
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
const accessToken = await this.resolveAccessToken();
|
|
1390
|
+
if (accessToken) {
|
|
1391
|
+
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
throw new ConfigError(
|
|
1395
|
+
"No credentials found.",
|
|
1396
|
+
"Run `okx auth login` to authenticate, or configure API key credentials."
|
|
1397
|
+
);
|
|
1398
|
+
}
|
|
1250
1399
|
/** The canonical base URL for this client (e.g. https://www.okx.com). */
|
|
1251
1400
|
get baseUrl() {
|
|
1252
1401
|
return this.config.baseUrl;
|
|
@@ -1305,18 +1454,6 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1305
1454
|
});
|
|
1306
1455
|
}
|
|
1307
1456
|
setAuthHeaders(headers, method, requestPath, bodyJson, timestamp) {
|
|
1308
|
-
if (!this.config.hasAuth) {
|
|
1309
|
-
throw new ConfigError(
|
|
1310
|
-
"Private endpoint requires API credentials.",
|
|
1311
|
-
"Configure OKX_API_KEY, OKX_SECRET_KEY and OKX_PASSPHRASE."
|
|
1312
|
-
);
|
|
1313
|
-
}
|
|
1314
|
-
if (!this.config.apiKey || !this.config.secretKey || !this.config.passphrase) {
|
|
1315
|
-
throw new ConfigError(
|
|
1316
|
-
"Invalid private API credentials state.",
|
|
1317
|
-
"Ensure all OKX credentials are set."
|
|
1318
|
-
);
|
|
1319
|
-
}
|
|
1320
1457
|
const payload = `${timestamp}${method.toUpperCase()}${requestPath}${bodyJson}`;
|
|
1321
1458
|
const signature = signOkxPayload(payload, this.config.secretKey);
|
|
1322
1459
|
headers.set("OK-ACCESS-KEY", this.config.apiKey);
|
|
@@ -1431,7 +1568,7 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1431
1568
|
const conn = this.pilot.getConnectionParams();
|
|
1432
1569
|
this.logRequest("POST", `${conn.baseUrl}${path4}`, "private");
|
|
1433
1570
|
const reqConfig = { method: "POST", path: path4, auth: "private" };
|
|
1434
|
-
const headers = this.buildHeaders(reqConfig, path4, bodyJson, getNow());
|
|
1571
|
+
const headers = await this.buildHeaders(reqConfig, path4, bodyJson, getNow());
|
|
1435
1572
|
if (conn.userAgent) {
|
|
1436
1573
|
headers.set("User-Agent", conn.userAgent);
|
|
1437
1574
|
}
|
|
@@ -1552,7 +1689,7 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1552
1689
|
// Header building
|
|
1553
1690
|
// ---------------------------------------------------------------------------
|
|
1554
1691
|
/** Build HTTP headers. reqConfig.extraHeaders must NOT contain auth keys (OK-ACCESS-*). */
|
|
1555
|
-
buildHeaders(reqConfig, requestPath, bodyJson, timestamp) {
|
|
1692
|
+
async buildHeaders(reqConfig, requestPath, bodyJson, timestamp) {
|
|
1556
1693
|
const headers = new Headers({
|
|
1557
1694
|
"Content-Type": "application/json",
|
|
1558
1695
|
Accept: "application/json"
|
|
@@ -1561,7 +1698,7 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1561
1698
|
headers.set("User-Agent", this.config.userAgent);
|
|
1562
1699
|
}
|
|
1563
1700
|
if (reqConfig.auth === "private") {
|
|
1564
|
-
this.
|
|
1701
|
+
await this.applyAuth(headers, reqConfig.method, requestPath, bodyJson, timestamp);
|
|
1565
1702
|
}
|
|
1566
1703
|
const useSimulated = reqConfig.simulatedTrading !== void 0 ? reqConfig.simulatedTrading : this.config.demo;
|
|
1567
1704
|
if (useSimulated) {
|
|
@@ -1615,7 +1752,7 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1615
1752
|
if (reqConfig.rateLimit) {
|
|
1616
1753
|
await this.rateLimiter.consume(reqConfig.rateLimit);
|
|
1617
1754
|
}
|
|
1618
|
-
const headers = this.buildHeaders(reqConfig, requestPath, bodyJson, timestamp);
|
|
1755
|
+
const headers = await this.buildHeaders(reqConfig, requestPath, bodyJson, timestamp);
|
|
1619
1756
|
if (conn.userAgent) {
|
|
1620
1757
|
headers.set("User-Agent", conn.userAgent);
|
|
1621
1758
|
}
|
|
@@ -1767,6 +1904,23 @@ function privateRateLimit(key, rps = 10) {
|
|
|
1767
1904
|
refillPerSecond: rps
|
|
1768
1905
|
};
|
|
1769
1906
|
}
|
|
1907
|
+
var CURSOR_PROPS = {
|
|
1908
|
+
after: { type: "string", description: "Cursor: return older records" },
|
|
1909
|
+
before: { type: "string", description: "Cursor: return newer records" }
|
|
1910
|
+
};
|
|
1911
|
+
var TIME_RANGE_PROPS = {
|
|
1912
|
+
begin: { type: "string", description: "Start time (ms)" },
|
|
1913
|
+
end: { type: "string", description: "End time (ms)" }
|
|
1914
|
+
};
|
|
1915
|
+
function readPaginationParams(args, readStr, readNum) {
|
|
1916
|
+
return {
|
|
1917
|
+
after: readStr(args, "after"),
|
|
1918
|
+
before: readStr(args, "before"),
|
|
1919
|
+
begin: readStr(args, "begin"),
|
|
1920
|
+
end: readStr(args, "end"),
|
|
1921
|
+
limit: readNum(args, "limit")
|
|
1922
|
+
};
|
|
1923
|
+
}
|
|
1770
1924
|
function assertNotDemo(config, endpoint) {
|
|
1771
1925
|
if (config.demo) {
|
|
1772
1926
|
throw new ConfigError(
|
|
@@ -2038,7 +2192,7 @@ var OKX_SITES = {
|
|
|
2038
2192
|
},
|
|
2039
2193
|
us: {
|
|
2040
2194
|
label: "US",
|
|
2041
|
-
apiBaseUrl: "https://
|
|
2195
|
+
apiBaseUrl: "https://us.okx.com",
|
|
2042
2196
|
webUrl: "https://app.okx.com"
|
|
2043
2197
|
}
|
|
2044
2198
|
};
|
|
@@ -3557,7 +3711,7 @@ function safeWriteFile(targetDir, fileName, data) {
|
|
|
3557
3711
|
throw new Error(`Invalid file name: "${fileName}"`);
|
|
3558
3712
|
}
|
|
3559
3713
|
const resolvedDir = resolve(targetDir);
|
|
3560
|
-
const filePath =
|
|
3714
|
+
const filePath = join4(resolvedDir, safeName);
|
|
3561
3715
|
const resolvedPath = resolve(filePath);
|
|
3562
3716
|
if (!resolvedPath.startsWith(resolvedDir + sep)) {
|
|
3563
3717
|
throw new Error(`Path traversal detected: "${fileName}" resolves outside target directory`);
|
|
@@ -3600,7 +3754,7 @@ async function downloadSkillZip(client, name, targetDir, format = "zip") {
|
|
|
3600
3754
|
return filePath;
|
|
3601
3755
|
}
|
|
3602
3756
|
var DEFAULT_MAX_TOTAL_BYTES = 100 * 1024 * 1024;
|
|
3603
|
-
var DEFAULT_REGISTRY_PATH =
|
|
3757
|
+
var DEFAULT_REGISTRY_PATH = join6(homedir4(), ".okx", "skills", "registry.json");
|
|
3604
3758
|
function registerSkillsTools() {
|
|
3605
3759
|
return [
|
|
3606
3760
|
{
|
|
@@ -5998,37 +6152,45 @@ function registerEventContractTools() {
|
|
|
5998
6152
|
{
|
|
5999
6153
|
name: "event_get_orders",
|
|
6000
6154
|
module: "event",
|
|
6001
|
-
description: "Query event contract orders
|
|
6155
|
+
description: "Query event contract orders (open, 7d history, or 3-month archive). outcome pre-translated (YES/NO/UP/DOWN). Do NOT use for trade executions \u2014 use event_get_fills for fill records and settlement outcomes.",
|
|
6002
6156
|
isWrite: false,
|
|
6003
6157
|
inputSchema: {
|
|
6004
6158
|
type: "object",
|
|
6005
6159
|
properties: {
|
|
6006
|
-
|
|
6160
|
+
status: {
|
|
6007
6161
|
type: "string",
|
|
6008
|
-
|
|
6162
|
+
enum: ["open", "history", "archive"],
|
|
6163
|
+
description: "open=active, history=7d (default), archive=3mo"
|
|
6009
6164
|
},
|
|
6010
|
-
|
|
6165
|
+
instId: {
|
|
6011
6166
|
type: "string",
|
|
6012
|
-
description: "
|
|
6167
|
+
description: "Event contract instrument ID"
|
|
6013
6168
|
},
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6169
|
+
ordType: { type: "string", description: "Order type filter" },
|
|
6170
|
+
state: { type: "string", description: "canceled|filled (only for history/archive)" },
|
|
6171
|
+
...CURSOR_PROPS,
|
|
6172
|
+
...TIME_RANGE_PROPS,
|
|
6173
|
+
limit: { type: "number", description: "Max results (default 100)" }
|
|
6018
6174
|
}
|
|
6019
6175
|
},
|
|
6020
6176
|
handler: async (rawArgs, context) => {
|
|
6021
6177
|
const args = asRecord(rawArgs);
|
|
6022
|
-
const
|
|
6023
|
-
const
|
|
6024
|
-
|
|
6178
|
+
const status = readString(args, "status") ?? "history";
|
|
6179
|
+
const endpointMap = {
|
|
6180
|
+
open: "/api/v5/trade/orders-pending",
|
|
6181
|
+
archive: "/api/v5/trade/orders-history-archive"
|
|
6182
|
+
};
|
|
6183
|
+
const endpoint = endpointMap[status] ?? "/api/v5/trade/orders-history";
|
|
6184
|
+
const params = compactObject({
|
|
6185
|
+
instType: "EVENTS",
|
|
6186
|
+
instId: readString(args, "instId"),
|
|
6187
|
+
ordType: readString(args, "ordType"),
|
|
6188
|
+
state: readString(args, "state"),
|
|
6189
|
+
...readPaginationParams(args, readString, readNumber)
|
|
6190
|
+
});
|
|
6025
6191
|
const response = await context.client.privateGet(
|
|
6026
6192
|
endpoint,
|
|
6027
|
-
|
|
6028
|
-
instType: "EVENTS",
|
|
6029
|
-
instId: readString(args, "instId"),
|
|
6030
|
-
limit: readNumber(args, "limit")
|
|
6031
|
-
}),
|
|
6193
|
+
params,
|
|
6032
6194
|
privateRateLimit("event_get_orders", 20)
|
|
6033
6195
|
);
|
|
6034
6196
|
const base = normalizeResponse(response);
|
|
@@ -6042,41 +6204,51 @@ function registerEventContractTools() {
|
|
|
6042
6204
|
stateLabel: mapOrderState(String(item["state"] ?? ""))
|
|
6043
6205
|
};
|
|
6044
6206
|
}) : base["data"];
|
|
6045
|
-
return { ...base, data };
|
|
6207
|
+
return { ...base, data, requestParams: params };
|
|
6046
6208
|
}
|
|
6047
6209
|
},
|
|
6048
6210
|
{
|
|
6049
6211
|
name: "event_get_fills",
|
|
6050
6212
|
module: "event",
|
|
6051
|
-
description: "Get event contract fill history. outcome pre-translated (YES/NO/UP/DOWN). Each record includes a 'type' field: 'fill' (
|
|
6213
|
+
description: "Get event contract fill history (trade executions and settlement payouts). archive=true for up to 3mo, false (default) for last 3d. outcome pre-translated (YES/NO/UP/DOWN). Each record includes a 'type' field: 'fill' (opening trade) or 'settlement' (expiry payout with settlementResult win/loss and pnl). Do NOT use for order status \u2014 use event_get_orders instead.",
|
|
6052
6214
|
isWrite: false,
|
|
6053
6215
|
inputSchema: {
|
|
6054
6216
|
type: "object",
|
|
6055
6217
|
properties: {
|
|
6218
|
+
archive: {
|
|
6219
|
+
type: "boolean",
|
|
6220
|
+
description: "true=up to 3mo, false=3d (default)"
|
|
6221
|
+
},
|
|
6056
6222
|
instId: {
|
|
6057
6223
|
type: "string",
|
|
6058
6224
|
description: "Event contract instrument ID"
|
|
6059
6225
|
},
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
}
|
|
6226
|
+
ordId: { type: "string", description: "Order ID filter" },
|
|
6227
|
+
...CURSOR_PROPS,
|
|
6228
|
+
...TIME_RANGE_PROPS,
|
|
6229
|
+
limit: { type: "number", description: "Max results (default 100 or 20 for archive)" }
|
|
6064
6230
|
}
|
|
6065
6231
|
},
|
|
6066
6232
|
handler: async (rawArgs, context) => {
|
|
6067
6233
|
const args = asRecord(rawArgs);
|
|
6234
|
+
const archive = readBoolean(args, "archive") ?? false;
|
|
6235
|
+
const path4 = archive ? "/api/v5/trade/fills-history" : "/api/v5/trade/fills";
|
|
6236
|
+
const paging = readPaginationParams(args, readString, readNumber);
|
|
6237
|
+
const params = compactObject({
|
|
6238
|
+
instType: "EVENTS",
|
|
6239
|
+
instId: readString(args, "instId"),
|
|
6240
|
+
ordId: readString(args, "ordId"),
|
|
6241
|
+
...paging,
|
|
6242
|
+
limit: paging.limit ?? (archive ? 20 : void 0)
|
|
6243
|
+
});
|
|
6068
6244
|
const response = await context.client.privateGet(
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
instType: "EVENTS",
|
|
6072
|
-
instId: readString(args, "instId"),
|
|
6073
|
-
limit: readNumber(args, "limit")
|
|
6074
|
-
}),
|
|
6245
|
+
path4,
|
|
6246
|
+
params,
|
|
6075
6247
|
privateRateLimit("event_get_fills", 20)
|
|
6076
6248
|
);
|
|
6077
6249
|
const base = normalizeResponse(response);
|
|
6078
6250
|
const data = Array.isArray(base["data"]) ? base["data"].map(enrichFill) : base["data"];
|
|
6079
|
-
return { ...base, data };
|
|
6251
|
+
return { ...base, data, requestParams: params };
|
|
6080
6252
|
}
|
|
6081
6253
|
},
|
|
6082
6254
|
// -----------------------------------------------------------------------
|
|
@@ -6160,7 +6332,7 @@ function registerEventContractTools() {
|
|
|
6160
6332
|
{
|
|
6161
6333
|
name: "event_amend_order",
|
|
6162
6334
|
module: "event",
|
|
6163
|
-
description: "Amend a pending event contract order (change price or size). [CAUTION] Modifies a real order. Before amending, call event_get_orders(
|
|
6335
|
+
description: "Amend a pending event contract order (change price or size). [CAUTION] Modifies a real order. Before amending, call event_get_orders(status=open) to obtain the ordId and confirm the order is still pending. Only limit/post_only orders can be amended.",
|
|
6164
6336
|
isWrite: true,
|
|
6165
6337
|
inputSchema: {
|
|
6166
6338
|
type: "object",
|
|
@@ -6191,7 +6363,7 @@ function registerEventContractTools() {
|
|
|
6191
6363
|
{
|
|
6192
6364
|
name: "event_cancel_order",
|
|
6193
6365
|
module: "event",
|
|
6194
|
-
description: "Cancel a pending event contract order. [CAUTION] Cancels a real order. Before cancelling, call event_get_orders(
|
|
6366
|
+
description: "Cancel a pending event contract order. [CAUTION] Cancels a real order. Before cancelling, call event_get_orders(status=open) to obtain the ordId and confirm the order is still pending. instId must be the full event contract instrument ID (e.g. BTC-ABOVE-DAILY-260224-1600-69700), NOT a spot trading pair.",
|
|
6195
6367
|
isWrite: true,
|
|
6196
6368
|
inputSchema: {
|
|
6197
6369
|
type: "object",
|
|
@@ -6239,27 +6411,27 @@ var PATH_SIGNAL_HISTORY = "/api/v5/journal/smartmoney/signal-history";
|
|
|
6239
6411
|
var SIGNAL_POOL_FILTER_PROPS = {
|
|
6240
6412
|
sortType: {
|
|
6241
6413
|
type: "string",
|
|
6242
|
-
description: "pnl
|
|
6414
|
+
description: "Pool ranking: pnl|pnlRatio (default pnl)"
|
|
6243
6415
|
},
|
|
6244
6416
|
period: {
|
|
6245
6417
|
type: "string",
|
|
6246
|
-
description: "3|7|30|90
|
|
6418
|
+
description: "Win-rate window days: 3|7|30|90 (default 90). Not snapshot range."
|
|
6247
6419
|
},
|
|
6248
6420
|
pnl: {
|
|
6249
6421
|
type: "string",
|
|
6250
|
-
description: "PNL_ANY|PNL_TOP50|PNL_TOP20|PNL_TOP5"
|
|
6422
|
+
description: "Top N% by PnL: PNL_ANY|PNL_TOP50|PNL_TOP20|PNL_TOP5 (default PNL_ANY)"
|
|
6251
6423
|
},
|
|
6252
6424
|
winRatio: {
|
|
6253
6425
|
type: "string",
|
|
6254
|
-
description: "WR_ANY|WR_GE_50|WR_GE_80"
|
|
6426
|
+
description: "Min win-rate: WR_ANY|WR_GE_50|WR_GE_80 (default WR_ANY)"
|
|
6255
6427
|
},
|
|
6256
6428
|
maxRetreat: {
|
|
6257
6429
|
type: "string",
|
|
6258
|
-
description: "MR_ANY|MR_LE_20|MR_LE_50"
|
|
6430
|
+
description: "Max drawdown: MR_ANY|MR_LE_20|MR_LE_50 (default MR_ANY)"
|
|
6259
6431
|
},
|
|
6260
6432
|
asset: {
|
|
6261
6433
|
type: "string",
|
|
6262
|
-
description: "AUM_ANY|AUM_TOP50|AUM_TOP20|AUM_TOP5"
|
|
6434
|
+
description: "Top N% by AUM: AUM_ANY|AUM_TOP50|AUM_TOP20|AUM_TOP5 (default AUM_ANY)"
|
|
6263
6435
|
}
|
|
6264
6436
|
};
|
|
6265
6437
|
var LEADERBOARD_POOL_FILTER_PROPS = {
|
|
@@ -6311,39 +6483,39 @@ function registerSmartmoneyTools() {
|
|
|
6311
6483
|
{
|
|
6312
6484
|
name: "smartmoney_get_overview",
|
|
6313
6485
|
module: "smartmoney",
|
|
6314
|
-
description: "Multi-currency smart money overview ranked by most-watched
|
|
6486
|
+
description: "Multi-currency smart money overview, ranked by tradersWithPosition DESC (most-watched first). Requires either ts (recommended, Date.now()) or dataVersion (yyyyMMddHHmm UTC) \u2014 at least one must be set; ts wins when both are sent. For single-currency signal with entry prices and trend, use smartmoney_get_signal.",
|
|
6315
6487
|
isWrite: false,
|
|
6316
6488
|
inputSchema: {
|
|
6317
6489
|
type: "object",
|
|
6318
6490
|
properties: {
|
|
6319
|
-
|
|
6491
|
+
ts: {
|
|
6320
6492
|
type: "string",
|
|
6321
|
-
description: "
|
|
6493
|
+
description: "Recommended. Timestamp ms \u2014 use Date.now() for latest."
|
|
6322
6494
|
},
|
|
6323
|
-
|
|
6495
|
+
dataVersion: {
|
|
6324
6496
|
type: "string",
|
|
6325
|
-
description: "
|
|
6497
|
+
description: "Alternative. yyyyMMddHHmm UTC for prior snapshot. If both sent, ts wins."
|
|
6326
6498
|
},
|
|
6327
6499
|
instType: {
|
|
6328
6500
|
type: "string",
|
|
6329
|
-
description: "SPOT|MARGIN|FUTURES|SWAP|OPTION"
|
|
6501
|
+
description: "SPOT|MARGIN|FUTURES|SWAP|OPTION (default SWAP)"
|
|
6330
6502
|
},
|
|
6331
6503
|
...SIGNAL_POOL_FILTER_PROPS,
|
|
6332
6504
|
lmtNum: {
|
|
6333
6505
|
type: "string",
|
|
6334
|
-
description: "Trader pool size 1-500"
|
|
6506
|
+
description: "Trader pool size 1-500 (default 100)"
|
|
6335
6507
|
},
|
|
6336
6508
|
instCcyList: {
|
|
6337
6509
|
type: "string",
|
|
6338
|
-
description: "Comma-separated e.g. BTC,ETH,SOL"
|
|
6510
|
+
description: "Comma-separated currency codes e.g. BTC,ETH,SOL (prefix-matched against instId)"
|
|
6339
6511
|
},
|
|
6340
6512
|
instCcy: {
|
|
6341
6513
|
type: "string",
|
|
6342
|
-
description: "Single currency e.g. BTC"
|
|
6514
|
+
description: "Single currency e.g. BTC; alias for instCcyList (instCcyList wins if both set)"
|
|
6343
6515
|
},
|
|
6344
6516
|
topInstruments: {
|
|
6345
6517
|
type: "string",
|
|
6346
|
-
description: "Top N instruments 1-100"
|
|
6518
|
+
description: "Top N instruments 1-100 (default 20)"
|
|
6347
6519
|
}
|
|
6348
6520
|
}
|
|
6349
6521
|
},
|
|
@@ -6375,7 +6547,7 @@ function registerSmartmoneyTools() {
|
|
|
6375
6547
|
{
|
|
6376
6548
|
name: "smartmoney_get_signal",
|
|
6377
6549
|
module: "smartmoney",
|
|
6378
|
-
description: "Single-currency consensus signal: long/short ratio, entry prices, trend, capital flow.
|
|
6550
|
+
description: "Single-currency consensus signal: long/short ratio, entry prices, trend, capital flow. Requires either instId (e.g. BTC-USDT-SWAP, recommended) or instCcy (SPOT/SWAP only) \u2014 instId wins when both are sent. Requires either ts (recommended, Date.now()) or dataVersion (yyyyMMddHHmm UTC) \u2014 at least one must be set; ts wins when both are sent. For multi-currency overview, use smartmoney_get_overview. For timeline, use smartmoney_get_signal_history.",
|
|
6379
6551
|
isWrite: false,
|
|
6380
6552
|
inputSchema: {
|
|
6381
6553
|
type: "object",
|
|
@@ -6386,24 +6558,24 @@ function registerSmartmoneyTools() {
|
|
|
6386
6558
|
},
|
|
6387
6559
|
instCcy: {
|
|
6388
6560
|
type: "string",
|
|
6389
|
-
description: "e.g. BTC
|
|
6561
|
+
description: "e.g. BTC (SPOT/SWAP only); instId takes precedence if both set"
|
|
6390
6562
|
},
|
|
6391
|
-
|
|
6563
|
+
ts: {
|
|
6392
6564
|
type: "string",
|
|
6393
|
-
description: "
|
|
6565
|
+
description: "Recommended. Timestamp ms \u2014 use Date.now() for latest."
|
|
6394
6566
|
},
|
|
6395
|
-
|
|
6567
|
+
dataVersion: {
|
|
6396
6568
|
type: "string",
|
|
6397
|
-
description: "
|
|
6569
|
+
description: "Alternative. yyyyMMddHHmm UTC for prior snapshot. If both sent, ts wins."
|
|
6398
6570
|
},
|
|
6399
6571
|
...SIGNAL_POOL_FILTER_PROPS,
|
|
6400
6572
|
lmtNum: {
|
|
6401
6573
|
type: "string",
|
|
6402
|
-
description: "Trader pool size 1-500"
|
|
6574
|
+
description: "Trader pool size 1-500 (default 100)"
|
|
6403
6575
|
},
|
|
6404
6576
|
authorIds: {
|
|
6405
6577
|
type: "string",
|
|
6406
|
-
description: "Comma-separated user IDs e.g. 1001,1002"
|
|
6578
|
+
description: "Comma-separated user IDs e.g. 1001,1002 \u2014 restricts the trader pool to these IDs only (precise filter)"
|
|
6407
6579
|
}
|
|
6408
6580
|
}
|
|
6409
6581
|
},
|
|
@@ -6439,7 +6611,7 @@ function registerSmartmoneyTools() {
|
|
|
6439
6611
|
{
|
|
6440
6612
|
name: "smartmoney_get_signal_history",
|
|
6441
6613
|
module: "smartmoney",
|
|
6442
|
-
description: "Signal history timeline sorted by ts DESC for trend analysis. Requires instId.
|
|
6614
|
+
description: "Signal history timeline sorted by ts DESC for trend analysis. Requires instId. Requires either ts (recommended, Date.now()) or dataVersion (yyyyMMddHHmm UTC) \u2014 at least one must be set; ts wins when both are sent. For current snapshot, use smartmoney_get_signal.",
|
|
6443
6615
|
isWrite: false,
|
|
6444
6616
|
inputSchema: {
|
|
6445
6617
|
type: "object",
|
|
@@ -6448,13 +6620,13 @@ function registerSmartmoneyTools() {
|
|
|
6448
6620
|
type: "string",
|
|
6449
6621
|
description: "e.g. BTC-USDT-SWAP"
|
|
6450
6622
|
},
|
|
6451
|
-
|
|
6623
|
+
ts: {
|
|
6452
6624
|
type: "string",
|
|
6453
|
-
description: "
|
|
6625
|
+
description: "Recommended. Timestamp ms \u2014 use Date.now() for latest."
|
|
6454
6626
|
},
|
|
6455
|
-
|
|
6627
|
+
dataVersion: {
|
|
6456
6628
|
type: "string",
|
|
6457
|
-
description: "
|
|
6629
|
+
description: "Alternative. yyyyMMddHHmm UTC for prior snapshot. If both sent, ts wins."
|
|
6458
6630
|
},
|
|
6459
6631
|
granularity: {
|
|
6460
6632
|
type: "string",
|
|
@@ -9967,7 +10139,7 @@ function toMcpTool(tool) {
|
|
|
9967
10139
|
};
|
|
9968
10140
|
}
|
|
9969
10141
|
function configFilePath() {
|
|
9970
|
-
return
|
|
10142
|
+
return join7(homedir5(), ".okx", "config.toml");
|
|
9971
10143
|
}
|
|
9972
10144
|
function readFullConfig() {
|
|
9973
10145
|
const path4 = configFilePath();
|
|
@@ -9986,11 +10158,6 @@ Or re-run: okx config init`
|
|
|
9986
10158
|
);
|
|
9987
10159
|
}
|
|
9988
10160
|
}
|
|
9989
|
-
function readTomlProfile(profileName) {
|
|
9990
|
-
const config = readFullConfig();
|
|
9991
|
-
const name = profileName ?? config.default_profile ?? "default";
|
|
9992
|
-
return config.profiles?.[name] ?? {};
|
|
9993
|
-
}
|
|
9994
10161
|
function expandShorthand(moduleId) {
|
|
9995
10162
|
if (moduleId === "all") return [...MODULES];
|
|
9996
10163
|
if (moduleId === "earn" || moduleId === "earn.all") return [...EARN_SUB_MODULE_IDS];
|
|
@@ -10024,18 +10191,24 @@ function parseModuleList(rawModules) {
|
|
|
10024
10191
|
}
|
|
10025
10192
|
return Array.from(deduped);
|
|
10026
10193
|
}
|
|
10027
|
-
function loadCredentials(toml) {
|
|
10194
|
+
async function loadCredentials(toml) {
|
|
10028
10195
|
const apiKey = process.env.OKX_API_KEY?.trim() ?? toml.api_key;
|
|
10029
10196
|
const secretKey = process.env.OKX_SECRET_KEY?.trim() ?? toml.secret_key;
|
|
10030
10197
|
const passphrase = process.env.OKX_PASSPHRASE?.trim() ?? toml.passphrase;
|
|
10031
|
-
const
|
|
10198
|
+
const hasApiKey = Boolean(apiKey && secretKey && passphrase);
|
|
10032
10199
|
const partialAuth = Boolean(apiKey) || Boolean(secretKey) || Boolean(passphrase);
|
|
10033
|
-
if (partialAuth && !
|
|
10200
|
+
if (partialAuth && !hasApiKey) {
|
|
10034
10201
|
throw new ConfigError(
|
|
10035
10202
|
"Partial API credentials detected.",
|
|
10036
10203
|
"Set OKX_API_KEY, OKX_SECRET_KEY and OKX_PASSPHRASE together (env vars or config.toml profile)."
|
|
10037
10204
|
);
|
|
10038
10205
|
}
|
|
10206
|
+
let hasOAuth = false;
|
|
10207
|
+
if (!hasApiKey) {
|
|
10208
|
+
const status = await execAuthStatus();
|
|
10209
|
+
hasOAuth = status?.status === "logged_in";
|
|
10210
|
+
}
|
|
10211
|
+
const hasAuth = hasOAuth || hasApiKey;
|
|
10039
10212
|
return { apiKey, secretKey, passphrase, hasAuth };
|
|
10040
10213
|
}
|
|
10041
10214
|
function resolveSite(cliSite, tomlSite) {
|
|
@@ -10069,9 +10242,11 @@ function resolveDemo(cli, toml) {
|
|
|
10069
10242
|
if (cli.demo === true) return true;
|
|
10070
10243
|
return process.env.OKX_DEMO === "1" || process.env.OKX_DEMO === "true" || (toml.demo ?? false);
|
|
10071
10244
|
}
|
|
10072
|
-
function loadConfig(cli) {
|
|
10073
|
-
const
|
|
10074
|
-
const
|
|
10245
|
+
async function loadConfig(cli) {
|
|
10246
|
+
const config = readFullConfig();
|
|
10247
|
+
const profileName = cli.profile ?? config.default_profile ?? "default";
|
|
10248
|
+
const toml = config.profiles?.[profileName] ?? {};
|
|
10249
|
+
const creds = await loadCredentials(toml);
|
|
10075
10250
|
const demo = resolveDemo(cli, toml);
|
|
10076
10251
|
const site = resolveSite(cli.site, toml.site);
|
|
10077
10252
|
const baseUrl = resolveBaseUrl(site, toml.base_url);
|
|
@@ -10091,6 +10266,7 @@ function loadConfig(cli) {
|
|
|
10091
10266
|
}
|
|
10092
10267
|
return {
|
|
10093
10268
|
...creds,
|
|
10269
|
+
profile: profileName,
|
|
10094
10270
|
baseUrl,
|
|
10095
10271
|
timeoutMs: Math.floor(rawTimeout),
|
|
10096
10272
|
modules: parseModuleList(cli.modules),
|
|
@@ -10103,7 +10279,7 @@ function loadConfig(cli) {
|
|
|
10103
10279
|
verbose: cli.verbose ?? false
|
|
10104
10280
|
};
|
|
10105
10281
|
}
|
|
10106
|
-
var CACHE_FILE =
|
|
10282
|
+
var CACHE_FILE = join8(homedir6(), ".okx", "update-check.json");
|
|
10107
10283
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
10108
10284
|
function readCache2() {
|
|
10109
10285
|
try {
|
|
@@ -10116,7 +10292,7 @@ function readCache2() {
|
|
|
10116
10292
|
}
|
|
10117
10293
|
function writeCache2(cache) {
|
|
10118
10294
|
try {
|
|
10119
|
-
mkdirSync6(
|
|
10295
|
+
mkdirSync6(join8(homedir6(), ".okx"), { recursive: true });
|
|
10120
10296
|
writeFileSync5(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
|
|
10121
10297
|
} catch {
|
|
10122
10298
|
}
|
|
@@ -10270,13 +10446,13 @@ function findMsStoreClaudePath() {
|
|
|
10270
10446
|
}
|
|
10271
10447
|
function getConfigPath(client) {
|
|
10272
10448
|
const home = os3.homedir();
|
|
10273
|
-
const
|
|
10449
|
+
const platform3 = process.platform;
|
|
10274
10450
|
switch (client) {
|
|
10275
10451
|
case "claude-desktop":
|
|
10276
|
-
if (
|
|
10452
|
+
if (platform3 === "win32") {
|
|
10277
10453
|
return findMsStoreClaudePath() ?? path3.join(appData(), "Claude", CLAUDE_CONFIG_FILE);
|
|
10278
10454
|
}
|
|
10279
|
-
if (
|
|
10455
|
+
if (platform3 === "darwin") {
|
|
10280
10456
|
return path3.join(home, "Library", "Application Support", "Claude", CLAUDE_CONFIG_FILE);
|
|
10281
10457
|
}
|
|
10282
10458
|
return path3.join(process.env.XDG_CONFIG_HOME ?? path3.join(home, ".config"), "Claude", CLAUDE_CONFIG_FILE);
|
|
@@ -10378,6 +10554,8 @@ function runSetup(options) {
|
|
|
10378
10554
|
`);
|
|
10379
10555
|
}
|
|
10380
10556
|
}
|
|
10557
|
+
var CACHE_PATH = join12(homedir10(), ".okx", "auth-binary-check.json");
|
|
10558
|
+
var CHECK_INTERVAL_MS2 = 2 * 60 * 60 * 1e3;
|
|
10381
10559
|
|
|
10382
10560
|
// src/constants.ts
|
|
10383
10561
|
import { createRequire } from "module";
|
|
@@ -10385,7 +10563,7 @@ var _require = createRequire(import.meta.url);
|
|
|
10385
10563
|
var pkg = _require("../package.json");
|
|
10386
10564
|
var SERVER_NAME = "okx-trade-mcp";
|
|
10387
10565
|
var SERVER_VERSION = pkg.version;
|
|
10388
|
-
var GIT_HASH = true ? "
|
|
10566
|
+
var GIT_HASH = true ? "a4277b3" : "dev";
|
|
10389
10567
|
|
|
10390
10568
|
// src/server.ts
|
|
10391
10569
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -10662,7 +10840,7 @@ async function main() {
|
|
|
10662
10840
|
`);
|
|
10663
10841
|
return;
|
|
10664
10842
|
}
|
|
10665
|
-
const config = loadConfig({
|
|
10843
|
+
const config = await loadConfig({
|
|
10666
10844
|
modules: cli.modules,
|
|
10667
10845
|
profile: cli.profile,
|
|
10668
10846
|
site: cli.site,
|